Compare commits
111 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f9ad542029 | |||
| b5b25a2ccb | |||
| 25683dcf39 | |||
| 02689d0d86 | |||
| 099d664731 | |||
| 1f7d695bba | |||
| 50327a4d83 | |||
| 9b35f6b23f | |||
| 5207b381b7 | |||
| 679ffd380b | |||
| de1f1c599f | |||
| d16707a2f3 | |||
| 740ace88f0 | |||
| d5d14e9684 | |||
| 086f45510f | |||
| e24024cbb2 | |||
| e7e444e9b2 | |||
| 8bfe8a2c37 | |||
| caec44c57d | |||
| f4a0d149ca | |||
| f3c22315a1 | |||
| 59da9211f6 | |||
| 82d26b5bb8 | |||
| 7c61859840 | |||
| 4c151a4cf1 | |||
| 85b71f1dd6 | |||
| 9474f7654c | |||
| 854d7bea1f | |||
| 7b8275100d | |||
| 24484496d2 | |||
| 190c98d8f2 | |||
| 046483b9da | |||
| 177cf89756 | |||
| e167a34f32 | |||
| bd1e624eb1 | |||
| 1db14631df | |||
| 59bf2749dd | |||
| ad0bcddaa4 | |||
| f485583621 | |||
| 49355fe32b | |||
| 356ae6cf74 | |||
| 770f76a658 | |||
| 42ba733cd4 | |||
| 45312af297 | |||
| baee73e132 | |||
| 279f40c82e | |||
| aaf0763e09 | |||
| c5fa49405f | |||
| 3d96e4adb6 | |||
| 42af6f2ec0 | |||
| 31812d524d | |||
| 21a2863a1a | |||
| bd643503b3 | |||
| 51a1fb1235 | |||
| 8f0eec7031 | |||
| 6dd36a1575 | |||
| 46595b1399 | |||
| 90962b2328 | |||
| edd50ef181 | |||
| ced6dca7a3 | |||
| 559aeb3c3f | |||
| 16131e0acc | |||
| e8f377c20d | |||
| 27896d53ef | |||
| a44d89f953 | |||
| 75465f2f21 | |||
| 3aadff7a3c | |||
| c65101537d | |||
| b35eee5561 | |||
| cdfe87c81b | |||
| 7e899d900a | |||
| a037e616bf | |||
| f7650bd6df | |||
| 716a35701c | |||
| 6032010847 | |||
| 5446795196 | |||
| 31ed540939 | |||
| e715fdc835 | |||
| 81f44969ee | |||
| fc35d888fc | |||
| eb01597dd3 | |||
| 572430cc2b | |||
| 7b263676e0 | |||
| c8123383d5 | |||
| bdd6c52e4e | |||
| de65cd88bc | |||
| 83ca51a99b | |||
| 1def7b7b8d | |||
| dadd6c598f | |||
| b891ec7793 | |||
| 5b823d8684 | |||
| b146fa326d | |||
| bab02fca8a | |||
| 0b6882c37f | |||
| b1072b7a50 | |||
| 70a039da83 | |||
| 7efb112058 | |||
| 9c2f300325 | |||
| 4d4f1f8351 | |||
| 9dc98192f4 | |||
| b1a9757b40 | |||
| 522f58511c | |||
| d98f9de5d4 | |||
| 7d9642a5b1 | |||
| dc08b6b92a | |||
| 220ccb2eac | |||
| eb8c59efb5 | |||
| f87e943a1a | |||
| 2f845b40ca | |||
| d0a5d13a0e | |||
| fff157d5ca |
@@ -73,7 +73,7 @@ body:
|
|||||||
label: Version
|
label: Version
|
||||||
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
||||||
options:
|
options:
|
||||||
- v1.11.5.0 (Winter Update 2025 Hotfix 1)
|
- v1.12.6.2 (Spring Update 2026)
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -4,6 +4,20 @@ name: Publish release
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
target:
|
||||||
|
description: "The git ref to checkout, build from and release"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
tag:
|
||||||
|
description: "The tag of the release"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
prerelease:
|
||||||
|
description: "Prerelease"
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
target:
|
target:
|
||||||
@@ -23,13 +37,13 @@ on:
|
|||||||
env:
|
env:
|
||||||
CI_DIR: 2049ef39-42a2-46d2-b513-ee6d2e3a7b15
|
CI_DIR: 2049ef39-42a2-46d2-b513-ee6d2e3a7b15
|
||||||
RELEASES: |
|
RELEASES: |
|
||||||
windows:client:Windows/Client
|
|
||||||
windows:server:Windows/Server
|
windows:server:Windows/Server
|
||||||
linux:client:Linux/Client
|
|
||||||
linux:server:Linux/Server
|
linux:server:Linux/Server
|
||||||
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
|
||||||
mac:server:Mac/Server
|
mac:server:Mac/Server
|
||||||
ARCHIVE_BASE_NAME: luacsforbarotrauma
|
windows:client:Windows/Client
|
||||||
|
linux:client:Linux/Client
|
||||||
|
mac:client:Mac/Client/Barotrauma.app/Contents/MacOS
|
||||||
|
ARCHIVE_BASE_NAME: luacsforbarotraumaEP
|
||||||
# XXX: these file names are subject to shell expansion.
|
# XXX: these file names are subject to shell expansion.
|
||||||
# Be careful when using special characters.
|
# Be careful when using special characters.
|
||||||
ARCHIVE_FILES_SERVER: |
|
ARCHIVE_FILES_SERVER: |
|
||||||
|
|||||||
@@ -60,3 +60,6 @@ Deploy/DeployAll/PrivateKey.*
|
|||||||
|
|
||||||
#Rider
|
#Rider
|
||||||
*.DotSettings.user
|
*.DotSettings.user
|
||||||
|
.vscode/settings.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/tasks.json
|
||||||
|
|||||||
@@ -2143,7 +2143,7 @@ namespace Barotrauma
|
|||||||
if (existingAffliction == null)
|
if (existingAffliction == null)
|
||||||
{
|
{
|
||||||
existingAffliction = afflictionPrefab.Instantiate(strength);
|
existingAffliction = afflictionPrefab.Instantiate(strength);
|
||||||
afflictions.Add(existingAffliction, limb);
|
afflictions.TryAdd(existingAffliction, limb);
|
||||||
newAdded = true;
|
newAdded = true;
|
||||||
}
|
}
|
||||||
existingAffliction.SetStrength(strength);
|
existingAffliction.SetStrength(strength);
|
||||||
|
|||||||
@@ -1388,12 +1388,12 @@ namespace Barotrauma
|
|||||||
if (me.SimPosition.Length() > 2000.0f)
|
if (me.SimPosition.Length() > 2000.0f)
|
||||||
{
|
{
|
||||||
NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange);
|
NewMessage("Removed " + me.Name + " (simposition " + me.SimPosition + ")", Color.Orange);
|
||||||
MapEntity.MapEntityList.RemoveAt(i);
|
MapEntity.MapEntityList.Remove(me);
|
||||||
}
|
}
|
||||||
else if (!me.ShouldBeSaved)
|
else if (!me.ShouldBeSaved)
|
||||||
{
|
{
|
||||||
NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange);
|
NewMessage("Removed " + me.Name + " (!ShouldBeSaved)", Color.Orange);
|
||||||
MapEntity.MapEntityList.RemoveAt(i);
|
MapEntity.MapEntityList.Remove(me);
|
||||||
}
|
}
|
||||||
else if (me is Item)
|
else if (me is Item)
|
||||||
{
|
{
|
||||||
@@ -3544,6 +3544,11 @@ namespace Barotrauma
|
|||||||
ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray()
|
ContentPackageManager.RegularPackages.Select(p => p.Name).ToArray()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
commands.Add(new Command("ShowServerPerf", "Immediately log server performance info", (string[] args) =>
|
||||||
|
{
|
||||||
|
// TODO: Not yet :)
|
||||||
|
}));
|
||||||
|
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
|
commands.Add(new Command("startdedicatedserver", "", (string[] args) =>
|
||||||
{
|
{
|
||||||
@@ -3577,6 +3582,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}));*/
|
}));*/
|
||||||
|
|
||||||
|
AssignOnClientExecute(
|
||||||
|
"ShowServerPerf",
|
||||||
|
(string[] args) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.SendConsoleCommand("ShowServerPerf");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
AssignOnClientExecute(
|
AssignOnClientExecute(
|
||||||
"giveperm",
|
"giveperm",
|
||||||
(string[] args) =>
|
(string[] args) =>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void DebugDraw(SpriteBatch spriteBatch)
|
public void DebugDraw(SpriteBatch spriteBatch)
|
||||||
{
|
{
|
||||||
foreach (Event ev in activeEvents)
|
foreach (Event ev in _activeEvents)
|
||||||
{
|
{
|
||||||
Vector2 drawPos = ev.DebugDrawPos;
|
Vector2 drawPos = ev.DebugDrawPos;
|
||||||
drawPos.Y = -drawPos.Y;
|
drawPos.Y = -drawPos.Y;
|
||||||
@@ -41,7 +41,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void DebugDrawHUD(SpriteBatch spriteBatch, float y)
|
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);
|
DrawEventTargetTags(spriteBatch, scriptedEvent);
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (isGraphHovered || isGraphSelected)
|
if (isGraphHovered || isGraphSelected)
|
||||||
{
|
{
|
||||||
foreach (var timeStamp in timeStamps)
|
foreach (var timeStamp in _timeStamps)
|
||||||
{
|
{
|
||||||
int t = (int)Math.Abs(Math.Round((timeStamp.Time - lastIntensityUpdate) / intensityGraphUpdateInterval));
|
int t = (int)Math.Abs(Math.Round((timeStamp.Time - lastIntensityUpdate) / intensityGraphUpdateInterval));
|
||||||
if (t == order)
|
if (t == order)
|
||||||
@@ -205,7 +205,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
adjustedYStep = GUI.AdjustForTextScale(12);
|
adjustedYStep = GUI.AdjustForTextScale(12);
|
||||||
foreach (EventSet eventSet in pendingEventSets)
|
foreach (EventSet eventSet in _pendingEventSets)
|
||||||
{
|
{
|
||||||
if (Submarine.MainSub == null) { break; }
|
if (Submarine.MainSub == null) { break; }
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ namespace Barotrauma
|
|||||||
y += yStep;
|
y += yStep;
|
||||||
|
|
||||||
adjustedYStep = GUI.AdjustForTextScale(18);
|
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);
|
GUI.DrawString(spriteBatch, new Vector2(x + 5, y), ev.ToString(), (!ev.IsFinished ? Color.White : Color.Red) * 0.8f, null, 0, GUIStyle.SmallFont);
|
||||||
|
|
||||||
|
|||||||
@@ -867,7 +867,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.DeconstructItems.Add(stackedItem);
|
Item.MarkForDeconstruction(stackedItem);
|
||||||
}
|
}
|
||||||
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
||||||
}
|
}
|
||||||
@@ -875,7 +875,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.DeconstructItems.Remove(stackedItem);
|
Item.UnmarkForDeconstruction(stackedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1933,7 +1933,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
void CalculateDistance()
|
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));
|
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(transducerPosition), ConvertUnits.ToSimUnits(worldPosition));
|
||||||
if (!path.Unreachable)
|
if (!path.Unreachable)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1892,7 +1892,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Item.DeconstructItems.Contains(item) &&
|
else if (Item.IsMarkedForDeconstruction(item) &&
|
||||||
OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder))
|
OrderPrefab.Prefabs.TryGet(Tags.DeconstructThis, out OrderPrefab deconstructOrder))
|
||||||
{
|
{
|
||||||
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
|
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public sealed class SettingControl : SettingBase, ISettingControl
|
|||||||
public SettingControl(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo)
|
public SettingControl(IConfigInfo configInfo, Func<OneOf<string, XElement, object>, bool> valueChangePredicate) : base(configInfo)
|
||||||
{
|
{
|
||||||
_valueChangePredicate = valueChangePredicate;
|
_valueChangePredicate = valueChangePredicate;
|
||||||
TrySetValue(configInfo.Element);
|
TrySetSerializedValue(configInfo.Element);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDispose()
|
protected override void OnDispose()
|
||||||
@@ -37,7 +37,7 @@ public sealed class SettingControl : SettingBase, ISettingControl
|
|||||||
public override string GetStringValue() => Value.ToString();
|
public override string GetStringValue() => Value.ToString();
|
||||||
public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString();
|
public override string GetDefaultStringValue() => new KeyOrMouse(Keys.NumLock).ToString();
|
||||||
|
|
||||||
public override bool TrySetValue(OneOf<string, XElement> value)
|
public override bool TrySetSerializedValue(OneOf<string, XElement> value)
|
||||||
{
|
{
|
||||||
var newVal = value.Match<KeyOrMouse>(
|
var newVal = value.Match<KeyOrMouse>(
|
||||||
(string v) => GetKeyOrMouse(v),
|
(string v) => GetKeyOrMouse(v),
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
using System;
|
using Barotrauma.CharacterEditor;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.LuaCs;
|
||||||
|
using Barotrauma.LuaCs.Data;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Barotrauma.CharacterEditor;
|
using static System.Collections.Specialized.BitVector32;
|
||||||
using Barotrauma.LuaCs;
|
|
||||||
using Barotrauma.LuaCs.Data;
|
|
||||||
using Barotrauma.Networking;
|
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
|
|
||||||
// ReSharper disable ObjectCreationAsStatement
|
// ReSharper disable ObjectCreationAsStatement
|
||||||
|
|
||||||
@@ -16,92 +18,31 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
partial class LuaCsSetup
|
partial class LuaCsSetup
|
||||||
{
|
{
|
||||||
private bool _isClientPromptActive;
|
public void PromptCSharpMods(Action<bool> onSelection, bool joiningServer)
|
||||||
private bool _isCsEnabledForSession = false;
|
|
||||||
|
|
||||||
public void CheckRunConditionalHostingCsEnabled(Action onReadyToRun)
|
|
||||||
{
|
{
|
||||||
var res = ReadyToRunNoPrompt();
|
ImmutableArray<ContentPackage> contentPackages = PackageManagementService.GetLoadedUnrestrictedPackages()
|
||||||
if (res.ShouldRun)
|
.Where(p => p.Name != PackageName)
|
||||||
{
|
|
||||||
onReadyToRun?.Invoke();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
DisplayCsModsPromptClient(res.Item2, (selectedYes) =>
|
|
||||||
{
|
|
||||||
if (selectedYes)
|
|
||||||
{
|
|
||||||
onReadyToRun?.Invoke();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private (bool ShouldRun, ImmutableArray<ContentPackage> PromptPackages) ReadyToRunNoPrompt()
|
|
||||||
{
|
|
||||||
if (this.IsCsEnabled)
|
|
||||||
{
|
|
||||||
return (true, ImmutableArray<ContentPackage>.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ShouldPromptForCs)
|
|
||||||
{
|
|
||||||
return (true, ImmutableArray<ContentPackage>.Empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImmutableArray<ContentPackage> contentPackages = PackageManagementService.GetLoadedAssemblyPackages()
|
|
||||||
.Where(p => p.Name != PackageId)
|
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
|
|
||||||
return (contentPackages.IsEmpty, contentPackages);
|
if (_csRunPolicy?.Value is "Enabled")
|
||||||
}
|
|
||||||
|
|
||||||
partial void CheckReadyToRun(Action onReadyToRun)
|
|
||||||
{
|
|
||||||
var res = ReadyToRunNoPrompt();
|
|
||||||
if (res.ShouldRun)
|
|
||||||
{
|
{
|
||||||
onReadyToRun?.Invoke();
|
IsCsEnabledForSession = true;
|
||||||
|
onSelection(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (_csRunPolicy?.Value is "Disabled")
|
||||||
|
{
|
||||||
|
IsCsEnabledForSession = false;
|
||||||
|
onSelection(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameMain.Client?.ClientPeer is P2POwnerPeer)
|
if (contentPackages.None())
|
||||||
{
|
{
|
||||||
SetCsPolicyAndContinue(true);
|
onSelection(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DisplayCsModsPromptClient(res.PromptPackages, (selectedYes) =>
|
|
||||||
{
|
|
||||||
SetCsPolicyAndContinue(selectedYes);
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
|
|
||||||
void SetCsPolicyAndContinue(bool csSessionExecutionPolicy)
|
|
||||||
{
|
|
||||||
var prevRunState = this.CurrentRunState;
|
|
||||||
if (CurrentRunState >= RunState.Running)
|
|
||||||
{
|
|
||||||
SetRunState(RunState.LoadedNoExec);
|
|
||||||
}
|
|
||||||
this._isCsEnabledForSession = csSessionExecutionPolicy;
|
|
||||||
CoroutineManager.Invoke(() =>
|
|
||||||
{
|
|
||||||
if (CurrentRunState != prevRunState)
|
|
||||||
{
|
|
||||||
SetRunState(prevRunState);
|
|
||||||
}
|
|
||||||
onReadyToRun?.Invoke();
|
|
||||||
}, 0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DisplayCsModsPromptClient(ImmutableArray<ContentPackage> contentPackages, Action<bool> onSelection)
|
|
||||||
{
|
|
||||||
if (_isClientPromptActive) { return; }
|
|
||||||
|
|
||||||
_isClientPromptActive = true;
|
|
||||||
|
|
||||||
GUIMessageBox messageBox = new GUIMessageBox(
|
GUIMessageBox messageBox = new GUIMessageBox(
|
||||||
TextManager.Get("warning"),
|
TextManager.Get("warning"),
|
||||||
relativeSize: new Vector2(0.3f, 0.55f),
|
relativeSize: new Vector2(0.3f, 0.55f),
|
||||||
@@ -115,7 +56,7 @@ namespace Barotrauma
|
|||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code",
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgBoxLayout.RectTransform), "The following mods contain CSharp code OR Unsandboxed Lua Code",
|
||||||
font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center);
|
font: GUIStyle.SubHeadingFont, wrap: true, textAlignment: Alignment.Center);
|
||||||
|
|
||||||
GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform))
|
GUIListBox packageListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), msgBoxLayout.RectTransform))
|
||||||
@@ -126,22 +67,39 @@ namespace Barotrauma
|
|||||||
foreach (ContentPackage package in contentPackages)
|
foreach (ContentPackage package in contentPackages)
|
||||||
{
|
{
|
||||||
GUIFrame packageFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), packageListBox.Content.RectTransform), style: "ListBoxElement");
|
GUIFrame packageFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), packageListBox.Content.RectTransform), style: "ListBoxElement");
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1f, 1f), packageFrame.RectTransform), package.Name);
|
GUILayoutGroup packageLayout = new GUILayoutGroup(new RectTransform(Vector2.One, packageFrame.RectTransform), true, Anchor.CenterLeft);
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), packageLayout.RectTransform), package.Name);
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.3f, 1f), packageLayout.RectTransform, Anchor.CenterRight), "Open Folder", style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (GUIButton button, object obj) =>
|
||||||
|
{
|
||||||
|
string directory = package.Dir;
|
||||||
|
if (string.IsNullOrEmpty(directory)) { return false; }
|
||||||
|
|
||||||
|
ToolBox.OpenFileWithShell(directory);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), "C# mods are not sandboxed, meaning that they have unrestrictive access to your computer, please make sure you trust these mods before you continue. If you are not hosting a server, selecting cancel will only run Lua mods.", wrap: true)
|
string bodyText =
|
||||||
|
joiningServer ?
|
||||||
|
"You are joining a server that includes mods with C# code OR unrestricted Lua code. These mods are not sandboxed and may access your computer without restrictions. If you trust these mods, select 'Enable C# for this session'. Otherwise, select 'Cancel' to run only Lua mods."
|
||||||
|
: "You have enabled mods that include C# code. These mods are not sandboxed and may access your computer without restrictions. If you trust these mods, select 'Enable C# for this session'. Otherwise, select 'Cancel' to run only Sandboxed Lua mods.";
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0f), msgBoxLayout.RectTransform), bodyText, wrap: true)
|
||||||
{
|
{
|
||||||
Wrap = true
|
Wrap = true
|
||||||
};
|
};
|
||||||
|
|
||||||
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), messageBox.Content.RectTransform, Anchor.BottomCenter), isHorizontal: false, childAnchor: Anchor.TopCenter);
|
GUILayoutGroup buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), messageBox.Content.RectTransform, Anchor.BottomCenter), isHorizontal: false, childAnchor: Anchor.TopCenter);
|
||||||
|
|
||||||
new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Continue")
|
new GUIButton(new RectTransform(new Vector2(0.8f, 0.0f), buttonLayout.RectTransform), "Enable C# for this session")
|
||||||
{
|
{
|
||||||
TextBlock = { AutoScaleHorizontal = true },
|
TextBlock = { AutoScaleHorizontal = true },
|
||||||
OnClicked = (btn, userdata) =>
|
OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
_isClientPromptActive = false;
|
IsCsEnabledForSession = true;
|
||||||
onSelection(true);
|
onSelection(true);
|
||||||
messageBox.Close();
|
messageBox.Close();
|
||||||
return true;
|
return true;
|
||||||
@@ -152,7 +110,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
OnClicked = (btn, userdata) =>
|
OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
_isClientPromptActive = false;
|
IsCsEnabledForSession = false;
|
||||||
onSelection(false);
|
onSelection(false);
|
||||||
messageBox.Close();
|
messageBox.Close();
|
||||||
return true;
|
return true;
|
||||||
@@ -201,10 +159,18 @@ namespace Barotrauma
|
|||||||
case SpriteEditorScreen:
|
case SpriteEditorScreen:
|
||||||
case SubEditorScreen:
|
case SubEditorScreen:
|
||||||
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated.
|
case TestScreen: // notes: TestScreen is a Linux edge case editor screen and is deprecated.
|
||||||
CheckReadyToRun(() =>
|
|
||||||
|
if (screen is NetLobbyScreen && CurrentRunState != RunState.Running && GameMain.Client?.ClientPeer is not P2POwnerPeer)
|
||||||
|
{
|
||||||
|
PromptCSharpMods(selection =>
|
||||||
|
{
|
||||||
|
SetRunState(RunState.Running);
|
||||||
|
}, joiningServer: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
SetRunState(RunState.Running);
|
SetRunState(RunState.Running);
|
||||||
});
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Logger.LogError(
|
Logger.LogError(
|
||||||
|
|||||||
@@ -32,11 +32,11 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
|
public bool? OnReceivedServerNetMessage(IReadMessage netMessage, ServerPacketHeader serverPacketHeader)
|
||||||
{
|
{
|
||||||
if (serverPacketHeader != ServerHeader)
|
if (serverPacketHeader != ServerHeader)
|
||||||
{
|
{
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte();
|
ServerToClient luaCsHeader = (ServerToClient)netMessage.ReadByte();
|
||||||
@@ -55,6 +55,8 @@ partial class NetworkingService : INetworkingService, IEventServerConnected, IEv
|
|||||||
ReadIds(netMessage);
|
ReadIds(netMessage);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendSyncMessage()
|
private void SendSyncMessage()
|
||||||
|
|||||||
+178
-32
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -20,23 +21,87 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
private string _selectedSearchQuery = string.Empty;
|
private string _selectedSearchQuery = string.Empty;
|
||||||
private ContentPackage _selectedContentPackage;
|
private ContentPackage _selectedContentPackage;
|
||||||
private string _selectedCategory = string.Empty;
|
private string _selectedCategory = string.Empty;
|
||||||
|
private ImmutableArray<ISettingBase> _currentlyDisplayedSettings;
|
||||||
|
private ILoggerService _loggerService;
|
||||||
|
|
||||||
|
private bool _promptOpen = false;
|
||||||
|
|
||||||
|
|
||||||
|
// Note: "static" instead of "const" for Hot Reload and to allow changing at runtime.
|
||||||
|
// ReSharper disable FieldCanBeMadeReadOnly.Local
|
||||||
|
|
||||||
|
// --- UI controls ---
|
||||||
|
private static float MenuTitleHeight = 0.06f; // (ContentDisplayAreaHeightContainer + MenuTitleHeight) < 1f
|
||||||
|
private static float ContentDisplayAreaHeightContainer = 0.93f;
|
||||||
|
private static float ContentDisplayAreaHeightInnerCategories = 0.99f;
|
||||||
|
private static float ContentDisplayAreaHeightInnerSettings = 0.97f;
|
||||||
|
private static float ContentLeftRightSplitPosition = 0.3f;
|
||||||
|
|
||||||
|
// Search Bar
|
||||||
|
private static float SearchBarLayoutHeight = 0.06f;
|
||||||
|
private static float SearchBarLabelWidth = 0.1f;
|
||||||
|
private static float SearchBarLabelBoxSpacing = 0.05f;
|
||||||
|
|
||||||
|
private static float SearchBarTextBoxWidth = 1f - SearchBarLabelWidth - SearchBarLabelBoxSpacing;
|
||||||
|
|
||||||
|
// Categories, Packages Display Area
|
||||||
|
private static float CategoriesDisplayListHeight = 0.945f;
|
||||||
|
private static float CategoryButtonHeightRelative = 0.122f;
|
||||||
|
private static float PackageSelectionButtonHeight = 0.07f;
|
||||||
|
|
||||||
|
private static Color CategoryButtonHoverSelectColor = new Color(50, 50, 50, 255);
|
||||||
|
private static Color CategoryButtonTextColor = Color.PeachPuff;
|
||||||
|
private static Color CategoryButtonTextColorSelected = Color.White;
|
||||||
|
private static Color CategoryButtonColorPressed = Color.TransparentBlack;
|
||||||
|
|
||||||
|
// Settings Display Area
|
||||||
|
private static float SettingLabelWidth = 0.6f;
|
||||||
|
private static float SettingControlWidth = 0.4f;
|
||||||
|
private static float SettingHeight = 0.05625f/ContentDisplayAreaHeightContainer/ContentDisplayAreaHeightInnerSettings;
|
||||||
|
private static Color SettingEntryLabelTextColor = Color.PeachPuff;
|
||||||
|
private static string SettingGUIFrameStyle = "";
|
||||||
|
private static Color? SettingGUIFrameColor = null;
|
||||||
|
|
||||||
|
// settings reset
|
||||||
|
private static Vector2 SettingsResetButtonTopSpacer = new Vector2(0f, 0.02f);
|
||||||
|
private static Vector2 SettingsResetButtonDimensions = new Vector2(0.3f, 0.05f);
|
||||||
|
private static string SettingsResetButtonStyle = "GUIButtonSmall";
|
||||||
|
private static Color SettingsResetButtonColor = Color.DarkOliveGreen;
|
||||||
|
private static Color SettingsResetButtonHoverColor = Color.Olive;
|
||||||
|
private static Color SettingsResetButtonTextColor = Color.PeachPuff;
|
||||||
|
private static Color SettingsResetButtonTextColorSelected = Color.White;
|
||||||
|
|
||||||
|
private static Vector2 ResetConfirmationPromptDimensions = new Vector2(0.15f, 0.2f);
|
||||||
|
|
||||||
|
|
||||||
|
// ReSharper restore FieldCanBeMadeReadOnly.Local
|
||||||
|
private const string SettingsResetButtonText = "LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings";
|
||||||
|
private const string SettingsResetPromptTitle = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title";
|
||||||
|
private const string SettingsResetPromptContents = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message";
|
||||||
|
private const string SettingsResetPromptYesText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes";
|
||||||
|
private const string SettingsResetPromptNoText = "LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No";
|
||||||
|
|
||||||
|
|
||||||
private event Action OnApplyInstalledModsChanges;
|
private event Action OnApplyInstalledModsChanges;
|
||||||
|
|
||||||
public ModsGameplaySettingsMenu(GUIFrame contentFrame,
|
public ModsGameplaySettingsMenu(GUIFrame contentFrame,
|
||||||
IPackageManagementService packageManagementService,
|
IPackageManagementService packageManagementService,
|
||||||
IConfigService configService,
|
IConfigService configService,
|
||||||
|
ILoggerService loggerService,
|
||||||
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
|
SettingsMenu settingsMenuInstance) : base(contentFrame, packageManagementService, configService, settingsMenuInstance)
|
||||||
{
|
{
|
||||||
_settingsInstancesGameplay = configService.GetDisplayableConfigs()
|
_settingsInstancesGameplay = configService.GetDisplayableConfigs()
|
||||||
.ToImmutableArray();
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
_loggerService = loggerService;
|
||||||
|
|
||||||
var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft);
|
var mainLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), contentFrame.RectTransform, Anchor.Center), false, Anchor.TopLeft);
|
||||||
// page title
|
// page title
|
||||||
var menuTitleLayoutGroup = new GUILayoutGroup(
|
var menuTitleLayoutGroup = new GUILayoutGroup(
|
||||||
new RectTransform(new Vector2(1f, 0.06f), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
new RectTransform(new Vector2(1f, MenuTitleHeight), mainLayoutGroup.RectTransform, Anchor.TopLeft), true, Anchor.TopLeft);
|
||||||
GUIUtil.Label(menuTitleLayoutGroup, "Mods Gameplay Settings", GUIStyle.LargeFont, new Vector2(1f, 1f));
|
GUIUtil.Label(menuTitleLayoutGroup,
|
||||||
|
GetLocalizedString("LuaCsForBarotrauma.SettingsMenu.ModGameplayButton", "Mod Gameplay Settings"),
|
||||||
|
GUIStyle.LargeFont, new Vector2(1f, 1f));
|
||||||
|
|
||||||
// page contents
|
// page contents
|
||||||
var contentAreaLayoutGroup = new GUILayoutGroup(
|
var contentAreaLayoutGroup = new GUILayoutGroup(
|
||||||
@@ -44,10 +109,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
Anchor.TopLeft);
|
Anchor.TopLeft);
|
||||||
|
|
||||||
var searchBarLayoutGroup = new GUILayoutGroup(
|
var searchBarLayoutGroup = new GUILayoutGroup(
|
||||||
new RectTransform(new Vector2(1f, 0.06f), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft);
|
new RectTransform(new Vector2(1f, SearchBarLayoutHeight), contentAreaLayoutGroup.RectTransform, Anchor.TopCenter), true, Anchor.CenterLeft);
|
||||||
GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(0.1f, 1f));
|
GUIUtil.Label(searchBarLayoutGroup, "Search: ", GUIStyle.SubHeadingFont, new Vector2(SearchBarLabelWidth, 1f));
|
||||||
var searchBar = new GUITextBox(
|
var searchBar = new GUITextBox(
|
||||||
new RectTransform(new Vector2(0.85f, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft),
|
new RectTransform(new Vector2(SearchBarTextBoxWidth, 0.1f), searchBarLayoutGroup.RectTransform, Anchor.TopLeft),
|
||||||
createClearButton: true)
|
createClearButton: true)
|
||||||
{
|
{
|
||||||
OnTextChangedDelegate = (btn, txt) =>
|
OnTextChangedDelegate = (btn, txt) =>
|
||||||
@@ -56,12 +121,13 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// main display area
|
// main display area
|
||||||
var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.90f), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter));
|
var settingsContentAreaGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, ContentDisplayAreaHeightContainer), contentAreaLayoutGroup.RectTransform, Anchor.BottomCenter));
|
||||||
GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One);
|
GUIUtil.Spacer(settingsContentAreaGroup, Vector2.One);
|
||||||
(_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true);
|
(_modCategoryDisplayGroup, _settingsDisplayGroup) = GUIUtil.CreateSidebars(settingsContentAreaGroup, true);
|
||||||
_modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(0.3f, 1f);
|
_modCategoryDisplayGroup.RectTransform.RelativeSize = new Vector2(ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerCategories);
|
||||||
_settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(0.7f, 1f);
|
_settingsDisplayGroup.RectTransform.RelativeSize = new Vector2(1f-ContentLeftRightSplitPosition, ContentDisplayAreaHeightInnerSettings);
|
||||||
|
|
||||||
// default category
|
// default category
|
||||||
_selectedCategory = "All";
|
_selectedCategory = "All";
|
||||||
@@ -202,10 +268,11 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
_selectedCategory = string.Empty;
|
_selectedCategory = string.Empty;
|
||||||
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
GenerateCategoryListDisplay(_modCategoryDisplayGroup, GetTargetPackagesList(), GetDisplayCategoriesList());
|
||||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||||
}, new Vector2(1f, 0.07f));
|
}, new Vector2(1f, PackageSelectionButtonHeight));
|
||||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.945f), layoutGroup.RectTransform));
|
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, CategoriesDisplayListHeight), layoutGroup.RectTransform));
|
||||||
const float entryHeight = 0.122f;
|
|
||||||
float sizeY = MathF.Max(categories.Length * entryHeight, 1f);
|
|
||||||
|
float sizeY = MathF.Max(categories.Length * CategoryButtonHeightRelative, 1f);
|
||||||
var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black)
|
var displayedCategoriesFrame = new GUIFrame(new RectTransform(new Vector2(1f, sizeY), containerBox.Content.RectTransform), style: null, color: Color.Black)
|
||||||
{
|
{
|
||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
@@ -214,17 +281,18 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
|
|
||||||
foreach (var category in categories)
|
foreach (var category in categories)
|
||||||
{
|
{
|
||||||
var btn = new GUIButton(new RectTransform(new Vector2(1f, entryHeight), displayCategoriesLayout.RectTransform),
|
var btn = new GUIButton(new RectTransform(new Vector2(1f, CategoryButtonHeightRelative), displayCategoriesLayout.RectTransform),
|
||||||
text: category, color: Color.TransparentBlack)
|
text: category, color: Color.TransparentBlack)
|
||||||
{
|
{
|
||||||
CanBeFocused = true,
|
CanBeFocused = true,
|
||||||
CanBeSelected = true,
|
CanBeSelected = true,
|
||||||
TextColor = Color.PeachPuff,
|
TextColor = CategoryButtonTextColor,
|
||||||
HoverColor = new Color(50, 50, 50, 255),
|
HoverColor = CategoryButtonHoverSelectColor,
|
||||||
HoverTextColor = Color.White,
|
HoverTextColor = CategoryButtonTextColorSelected,
|
||||||
SelectedColor = new Color(50, 50, 50, 255),
|
PressedColor = CategoryButtonColorPressed,
|
||||||
SelectedTextColor = Color.White,
|
SelectedColor = CategoryButtonHoverSelectColor,
|
||||||
OnPressed = () =>
|
SelectedTextColor = CategoryButtonHoverSelectColor,
|
||||||
|
OnClicked = (btn, obj) =>
|
||||||
{
|
{
|
||||||
_selectedCategory = category;
|
_selectedCategory = category;
|
||||||
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
GenerateSettingsListDisplay(_settingsDisplayGroup, GetDisplaySettingsList());
|
||||||
@@ -237,26 +305,47 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
|
void GenerateSettingsListDisplay(GUILayoutGroup layoutGroup, ImmutableArray<ISettingBase> settings)
|
||||||
{
|
{
|
||||||
layoutGroup.ClearChildren();
|
layoutGroup.ClearChildren();
|
||||||
const float settingHeight = 0.0625f;
|
_currentlyDisplayedSettings = settings;
|
||||||
|
|
||||||
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), layoutGroup.RectTransform));
|
var containerBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f-SettingsResetButtonDimensions.Y), layoutGroup.RectTransform));
|
||||||
foreach (var setting in settings)
|
foreach (var setting in settings)
|
||||||
{
|
{
|
||||||
var entry = AddSettingToDisplay(
|
var entry = AddSettingToDisplay(
|
||||||
setting,
|
setting,
|
||||||
containerBox.Content.RectTransform,
|
containerBox.Content.RectTransform,
|
||||||
settingHeight: settingHeight,
|
settingHeight: SettingHeight,
|
||||||
labelSize: new Vector2(0.6f, 1f),
|
labelSize: new Vector2(SettingLabelWidth, 1f),
|
||||||
controlSize: new Vector2(0.4f, 1f));
|
controlSize: new Vector2(SettingControlWidth, 1f));
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var spacer = new GUIFrame(new RectTransform(SettingsResetButtonTopSpacer, layoutGroup.RectTransform),
|
||||||
|
style: null, color: Color.TransparentBlack);
|
||||||
|
|
||||||
|
var resetSettingsButton = new GUIButton(
|
||||||
|
new RectTransform(SettingsResetButtonDimensions, layoutGroup.RectTransform),
|
||||||
|
GetLocalizedString(SettingsResetButtonText, "Reset Visible Settings"),
|
||||||
|
style: SettingsResetButtonStyle)
|
||||||
|
{
|
||||||
|
CanBeSelected = true,
|
||||||
|
CanBeFocused = true,
|
||||||
|
Color = SettingsResetButtonColor,
|
||||||
|
HoverColor = SettingsResetButtonHoverColor,
|
||||||
|
SelectedColor = SettingsResetButtonHoverColor,
|
||||||
|
SelectedTextColor = SettingsResetButtonTextColorSelected,
|
||||||
|
TextColor = SettingsResetButtonTextColor,
|
||||||
|
OnClicked = (btn, obj) =>
|
||||||
|
{
|
||||||
|
DisplayResetConfirmationPrompt(settings);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup) AddSettingToDisplay(ISettingBase setting,
|
(GUIFrame entryFrame, GUILayoutGroup entryLayoutGroup)
|
||||||
RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize)
|
AddSettingToDisplay(ISettingBase setting, RectTransform parent, float settingHeight, Vector2 labelSize, Vector2 controlSize)
|
||||||
{
|
{
|
||||||
GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent))
|
GUIFrame entryFrame = new GUIFrame(new RectTransform(new Vector2(1f, settingHeight), parent),
|
||||||
|
style: SettingGUIFrameStyle, color: SettingGUIFrameColor)
|
||||||
{
|
{
|
||||||
Color = Color.DarkGray
|
Color = Color.DarkGray
|
||||||
};
|
};
|
||||||
@@ -266,9 +355,10 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform),
|
new GUIFrame(new RectTransform(new Vector2(0.02f, 1f), entryLayoutGroup.RectTransform),
|
||||||
color: Color.TransparentBlack);
|
color: Color.TransparentBlack);
|
||||||
|
|
||||||
|
// setting label
|
||||||
new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform),
|
new GUITextBlock(new RectTransform(labelSize - new Vector2(0.05f, 0f), entryLayoutGroup.RectTransform),
|
||||||
GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName),
|
GetLocalizedString(setting.GetDisplayInfo().DisplayName, setting.GetDisplayInfo().DisplayName),
|
||||||
textColor: Color.PeachPuff,
|
textColor: SettingEntryLabelTextColor,
|
||||||
font: GUIStyle.SmallFont,
|
font: GUIStyle.SmallFont,
|
||||||
textAlignment: Alignment.Left)
|
textAlignment: Alignment.Left)
|
||||||
{
|
{
|
||||||
@@ -281,6 +371,58 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
});
|
});
|
||||||
return (entryFrame, entryLayoutGroup);
|
return (entryFrame, entryLayoutGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DisplayResetConfirmationPrompt(ImmutableArray<ISettingBase> settings)
|
||||||
|
{
|
||||||
|
if (_promptOpen)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_promptOpen = true;
|
||||||
|
|
||||||
|
var msgBox = new GUIMessageBox(GetLocalizedString(SettingsResetPromptTitle, "Reset Visible Settings"),
|
||||||
|
GetLocalizedString(SettingsResetPromptContents,
|
||||||
|
"Are you sure you want to reset the values for currently displayed settings?"),
|
||||||
|
new LocalizedString[]
|
||||||
|
{
|
||||||
|
GetLocalizedString(SettingsResetPromptYesText, "Yes"),
|
||||||
|
GetLocalizedString(SettingsResetPromptNoText, "No")
|
||||||
|
}, ResetConfirmationPromptDimensions);
|
||||||
|
msgBox.Buttons[0].OnClicked = (btn, obj) =>
|
||||||
|
{
|
||||||
|
ResetValuesForDisplayedSettings(settings);
|
||||||
|
btn.Visible = false;
|
||||||
|
_promptOpen = false;
|
||||||
|
msgBox.Close();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
msgBox.Buttons[1].OnClicked = (btn, obj) =>
|
||||||
|
{
|
||||||
|
btn.Visible = false;
|
||||||
|
_promptOpen = false;
|
||||||
|
msgBox.Close();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ResetValuesForDisplayedSettings(ImmutableArray<ISettingBase> settings)
|
||||||
|
{
|
||||||
|
if (settings.IsDefaultOrEmpty)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
NewValuesCache.Clear();
|
||||||
|
foreach (var setting in settings)
|
||||||
|
{
|
||||||
|
var str = setting.GetDefaultStringValue();
|
||||||
|
NewValuesCache[setting] = str;
|
||||||
|
loggerService.LogDebug($"Resetting value for {setting.InternalName} to '{str}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyInstalledModChanges();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -303,8 +445,12 @@ internal sealed class ModsGameplaySettingsMenu : ModsSettingsMenuBase
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
kvp.Key.TrySetValue(kvp.Value);
|
var success = kvp.Key.TrySetSerializedValue(kvp.Value);
|
||||||
ConfigService.SaveConfigValue(kvp.Key);
|
if (success)
|
||||||
|
{
|
||||||
|
ConfigService.SaveConfigValue(kvp.Key);
|
||||||
|
_loggerService.LogDebug($"Applied save value for {kvp.Key.InternalName} of {kvp.Value.ToString()}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
NewValuesCache.Clear();
|
NewValuesCache.Clear();
|
||||||
OnApplyInstalledModsChanges?.Invoke();
|
OnApplyInstalledModsChanges?.Invoke();
|
||||||
|
|||||||
+3
-1
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.LuaCs.Data;
|
using Barotrauma.LuaCs.Data;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using OneOf;
|
||||||
|
|
||||||
namespace Barotrauma.LuaCs;
|
namespace Barotrauma.LuaCs;
|
||||||
|
|
||||||
@@ -12,7 +14,7 @@ internal abstract class ModsSettingsMenuBase : IDisposable
|
|||||||
protected IPackageManagementService PackageManagementService { get; private set; }
|
protected IPackageManagementService PackageManagementService { get; private set; }
|
||||||
protected IConfigService ConfigService { get; private set; }
|
protected IConfigService ConfigService { get; private set; }
|
||||||
protected SettingsMenu SettingsMenuInstance { get; private set; }
|
protected SettingsMenu SettingsMenuInstance { get; private set; }
|
||||||
protected readonly ConcurrentDictionary<ISettingBase, string> NewValuesCache = new();
|
protected readonly ConcurrentDictionary<ISettingBase, OneOf<string, XElement>> NewValuesCache = new();
|
||||||
|
|
||||||
protected ModsSettingsMenuBase(GUIFrame contentFrame,
|
protected ModsSettingsMenuBase(GUIFrame contentFrame,
|
||||||
IPackageManagementService packageManagementService,
|
IPackageManagementService packageManagementService,
|
||||||
|
|||||||
+6
-3
@@ -18,12 +18,14 @@ public class SettingsMenuSystem : ISettingsMenuSystem
|
|||||||
private readonly Harmony _harmony;
|
private readonly Harmony _harmony;
|
||||||
private readonly IPackageManagementService _packageManagementService;
|
private readonly IPackageManagementService _packageManagementService;
|
||||||
private readonly IConfigService _configService;
|
private readonly IConfigService _configService;
|
||||||
|
private readonly ILoggerService _loggerService;
|
||||||
private static SettingsMenuSystem SystemInstance;
|
private static SettingsMenuSystem SystemInstance;
|
||||||
|
|
||||||
public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService)
|
public SettingsMenuSystem(IPackageManagementService packageManagementService, IConfigService configService, ILoggerService loggerService)
|
||||||
{
|
{
|
||||||
_packageManagementService = packageManagementService;
|
_packageManagementService = packageManagementService;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
|
_loggerService = loggerService;
|
||||||
SystemInstance = this;
|
SystemInstance = this;
|
||||||
_harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem));
|
_harmony = Harmony.CreateAndPatchAll(typeof(SettingsMenuSystem));
|
||||||
}
|
}
|
||||||
@@ -44,12 +46,13 @@ public class SettingsMenuSystem : ISettingsMenuSystem
|
|||||||
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
|
var tabControlsIndex = (SettingsMenu.Tab)tabCount+1;
|
||||||
|
|
||||||
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
|
_gameplayContentFrame = CreateNewContentTab(tabGameplayIndex, __instance,
|
||||||
"SettingsMenuTab.Mods", "LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
|
GUIStyle.ComponentStyles.ContainsKey("SettingsMenuTab.LuaCsSettings") ? "SettingsMenuTab.LuaCsSettings" : "SettingsMenuTab.Mods",
|
||||||
|
"LuaCsForBarotrauma.SettingsMenu.ModGameplayButton");
|
||||||
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
|
/*_controlsContentFrame = CreateNewContentTab(tabControlsIndex, __instance,
|
||||||
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
|
"SettingsMenuTab.Controls", "LuaCsForBarotrauma.SettingsMenu.ModControlsButton");
|
||||||
*/
|
*/
|
||||||
|
|
||||||
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, __instance);
|
_gameplayMenuInstance = new ModsGameplaySettingsMenu(_gameplayContentFrame, _packageManagementService, _configService, _loggerService, __instance);
|
||||||
//_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance);
|
//_controlsMenuInstance = new ModsControlsSettingsMenu(_controlsContentFrame, _packageManagementService, _configService, __instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -471,11 +471,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (item0 == null && item1 != null)
|
if (item0 == null && item1 != null)
|
||||||
{
|
{
|
||||||
item0 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
item0 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
||||||
}
|
}
|
||||||
else if (item0 != null && item1 == null)
|
else if (item0 != null && item1 == null)
|
||||||
{
|
{
|
||||||
item1 = Item.ItemList.Find(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
item1 = Item.ItemList.FirstOrDefault(it => it.GetComponent<ConnectionPanel>()?.DisconnectedWires.Contains(wire) ?? false);
|
||||||
}
|
}
|
||||||
if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1))
|
if (item0 != null && item1 != null && SelectedList.Contains(item0) && SelectedList.Contains(item1))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void Draw(SpriteBatch spriteBatch, bool editing = false)
|
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)
|
foreach (MapEntity e in entitiesToRender)
|
||||||
{
|
{
|
||||||
@@ -115,7 +115,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DrawFront(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
|
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)
|
foreach (MapEntity e in entitiesToRender)
|
||||||
{
|
{
|
||||||
@@ -164,7 +164,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
|
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();
|
depthSortedDamageable.Clear();
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
|
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)
|
foreach (MapEntity e in entitiesToRender)
|
||||||
{
|
{
|
||||||
@@ -217,7 +217,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
|
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)
|
foreach (MapEntity e in entitiesToRender)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class EntitySpawner : Entity, IServerSerializable
|
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)
|
public void ClientEventRead(IReadMessage message, float sendingTime)
|
||||||
{
|
{
|
||||||
@@ -34,7 +54,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
DebugConsole.Log("Received entity removal message for ID " + entityId + ". Entity with a matching ID not found.");
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -57,7 +77,7 @@ namespace Barotrauma
|
|||||||
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier);
|
GameAnalyticsManager.AddDesignEvent("ItemFabricated:" + (GameMain.GameSession?.GameMode?.Preset.Identifier ?? "none".ToIdentifier()) + ":" + newItem.Prefab.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
receivedEvents.Add((newItem, false));
|
receivedEventsQueue.Enqueue((newItem, false));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case (byte)SpawnableType.Character:
|
case (byte)SpawnableType.Character:
|
||||||
@@ -68,7 +88,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
receivedEvents.Add((character, false));
|
receivedEventsQueue.Enqueue((character, false));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -3916,7 +3916,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
errorLines.Add("");
|
errorLines.Add("");
|
||||||
errorLines.Add("EntitySpawner events:");
|
errorLines.Add("EntitySpawner events:");
|
||||||
foreach ((Entity entity, bool isRemoval) in Entity.Spawner.receivedEvents)
|
foreach ((Entity entity, bool isRemoval) in Entity.Spawner.GetReceivedEventsSnapshot())
|
||||||
{
|
{
|
||||||
errorLines.Add(
|
errorLines.Add(
|
||||||
(isRemoval ? "Remove " : "Create ") +
|
(isRemoval ? "Remove " : "Create ") +
|
||||||
|
|||||||
@@ -1015,25 +1015,20 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void TryStartServer()
|
private void TryStartServer()
|
||||||
{
|
{
|
||||||
LuaCsSetup.Instance.CheckRunConditionalHostingCsEnabled(() =>
|
if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
|
||||||
{
|
{
|
||||||
if (SubmarineInfo.SavedSubmarines.Any(s => s.CalculatingHash))
|
var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
|
||||||
|
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
|
||||||
|
waitBox.Buttons[0].OnClicked += (btn, userdata) =>
|
||||||
{
|
{
|
||||||
var waitBox = new GUIMessageBox(TextManager.Get("pleasewait"), TextManager.Get("waitforsubmarinehashcalculations"), new LocalizedString[] { TextManager.Get("cancel") });
|
CoroutineManager.StopCoroutines(waitCoroutine);
|
||||||
var waitCoroutine = CoroutineManager.StartCoroutine(WaitForSubmarineHashCalculations(waitBox), "WaitForSubmarineHashCalculations");
|
return true;
|
||||||
waitBox.Buttons[0].OnClicked += (btn, userdata) =>
|
};
|
||||||
{
|
}
|
||||||
CoroutineManager.StopCoroutines(waitCoroutine);
|
else
|
||||||
return true;
|
{
|
||||||
};
|
StartServer();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
StartServer();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
|
private IEnumerable<CoroutineStatus> WaitForSubmarineHashCalculations(GUIMessageBox messageBox)
|
||||||
|
|||||||
@@ -174,9 +174,9 @@ namespace Barotrauma
|
|||||||
public static RectTransform NewItemRectT(GUILayoutGroup parent)
|
public static RectTransform NewItemRectT(GUILayoutGroup parent)
|
||||||
=> new RectTransform((1.0f, 0.06f), parent.RectTransform, Anchor.CenterLeft);
|
=> new RectTransform((1.0f, 0.06f), parent.RectTransform, Anchor.CenterLeft);
|
||||||
|
|
||||||
public static void Spacer(GUILayoutGroup parent)
|
public static void Spacer(GUILayoutGroup parent, float height = 0.03f)
|
||||||
{
|
{
|
||||||
new GUIFrame(new RectTransform((1.0f, 0.03f), parent.RectTransform, Anchor.CenterLeft), style: null);
|
new GUIFrame(new RectTransform((1.0f, height), parent.RectTransform, Anchor.CenterLeft), style: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font)
|
public static GUITextBlock Label(GUILayoutGroup parent, LocalizedString str, GUIFont font)
|
||||||
@@ -507,6 +507,47 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#if OSX
|
||||||
|
Spacer(voiceChat, 0.003f);
|
||||||
|
|
||||||
|
// On macOS, microphone permission can apparently sometimes end up in a broken state when the app binary changes (eg. after a Steam update).
|
||||||
|
// The device seems to be there, but won't receive anything, even if the mic permission is fine.
|
||||||
|
// This button lets the user reset it and reboot the game, so the mic permission check will be retriggered on next run.
|
||||||
|
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), voiceChat.RectTransform),
|
||||||
|
text: TextManager.Get("MacResetMicPermissions"),
|
||||||
|
style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("MacResetMicPermissionsToolTip"),
|
||||||
|
OnClicked = (btn, obj) =>
|
||||||
|
{
|
||||||
|
var confirmBox = new GUIMessageBox(
|
||||||
|
TextManager.Get("MacResetMicPermissions"),
|
||||||
|
TextManager.Get("MacResetMicPermissionsConfirm"),
|
||||||
|
[TextManager.Get("OK"), TextManager.Get("Cancel")]);
|
||||||
|
confirmBox.Buttons[0].OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "tccutil",
|
||||||
|
Arguments = "reset Microphone com.FakeFish.Barotrauma",
|
||||||
|
UseShellExecute = false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DebugConsole.NewMessage($"Failed to reset microphone permission: {e.Message}", Color.Orange);
|
||||||
|
}
|
||||||
|
GameMain.Instance.Exit();
|
||||||
|
confirmBox.Close();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
confirmBox.Buttons[1].OnClicked = confirmBox.Close;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#endif
|
||||||
Spacer(voiceChat);
|
Spacer(voiceChat);
|
||||||
|
|
||||||
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);
|
Label(voiceChat, TextManager.Get("VCInputMode"), GUIStyle.SubHeadingFont);
|
||||||
|
|||||||
@@ -503,86 +503,102 @@ namespace Barotrauma.Sounds
|
|||||||
mutex = new object();
|
mutex = new object();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use the playingChannels lock to protect both channel assignment AND OpenAL operations.
|
||||||
|
// This prevents race conditions when multiple threads try to play sounds simultaneously
|
||||||
|
// (e.g., during Parallel.ForEach in MapEntity.UpdateAll).
|
||||||
|
int poolIndex = (int)sound.SourcePoolIndex;
|
||||||
|
object channelsLock = sound.Owner.GetPlayingChannelsLock(sound.SourcePoolIndex);
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
if (mutex != null) { Monitor.Enter(mutex); }
|
lock (channelsLock)
|
||||||
if (sound.Owner.CountPlayingInstances(sound) < sound.MaxSimultaneousInstances)
|
|
||||||
{
|
{
|
||||||
ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this);
|
if (mutex != null) { Monitor.Enter(mutex); }
|
||||||
}
|
try
|
||||||
|
|
||||||
if (ALSourceIndex >= 0)
|
|
||||||
{
|
|
||||||
if (!IsStream)
|
|
||||||
{
|
{
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, 0);
|
if (sound.Owner.CountPlayingInstancesUnsafe(sound, poolIndex) < sound.MaxSimultaneousInstances)
|
||||||
int alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
ALSourceIndex = sound.Owner.AssignFreeSourceToChannelUnsafe(this, poolIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sound.FillAlBuffers();
|
if (ALSourceIndex >= 0)
|
||||||
if (Sound.Buffers is not { AlBuffer: not 0, AlMuffledBuffer: not 0 }) { return; }
|
|
||||||
|
|
||||||
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffled ? Sound.Buffers.AlMuffledBuffer : Sound.Buffers.AlBuffer;
|
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
|
||||||
alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
{
|
||||||
throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + alBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
|
if (!IsStream)
|
||||||
}
|
{
|
||||||
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, 0);
|
||||||
|
int alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
SetProperties();
|
Sound.FillAlBuffers();
|
||||||
|
if (Sound.Buffers is not { AlBuffer: not 0, AlMuffledBuffer: not 0 }) { return; }
|
||||||
|
|
||||||
Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
|
uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffled ? Sound.Buffers.AlMuffledBuffer : Sound.Buffers.AlBuffer;
|
||||||
alError = Al.GetError();
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
||||||
if (alError != Al.NoError)
|
alError = Al.GetError();
|
||||||
{
|
if (alError != Al.NoError)
|
||||||
throw new Exception("Failed to play source: " + debugName + ", " + Al.GetErrorString(alError));
|
{
|
||||||
|
throw new Exception("Failed to bind buffer to source (" + ALSourceIndex.ToString() + ":" + sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex) + "," + alBuffer.ToString() + "): " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
SetProperties();
|
||||||
|
|
||||||
|
Al.SourcePlay(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
|
||||||
|
alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to play source: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
uint alBuffer = 0;
|
||||||
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
||||||
|
int alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Looping, Al.False);
|
||||||
|
alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to set stream looping state: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
streamShortBuffer = new short[STREAM_BUFFER_SIZE];
|
||||||
|
|
||||||
|
streamBuffers = new uint[4];
|
||||||
|
unqueuedBuffers = new uint[4];
|
||||||
|
streamBufferAmplitudes = new float[4];
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
Al.GenBuffer(out streamBuffers[i]);
|
||||||
|
|
||||||
|
alError = Al.GetError();
|
||||||
|
if (alError != Al.NoError)
|
||||||
|
{
|
||||||
|
throw new Exception("Failed to generate stream buffers: " + debugName + ", " + Al.GetErrorString(alError));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Al.IsBuffer(streamBuffers[i]))
|
||||||
|
{
|
||||||
|
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sound.Owner.InitUpdateChannelThread();
|
||||||
|
SetProperties();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
finally
|
||||||
{
|
{
|
||||||
uint alBuffer = 0;
|
if (mutex != null) { Monitor.Exit(mutex); }
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
|
|
||||||
int alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to reset source buffer: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Looping, Al.False);
|
|
||||||
alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to set stream looping state: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
streamShortBuffer = new short[STREAM_BUFFER_SIZE];
|
|
||||||
|
|
||||||
streamBuffers = new uint[4];
|
|
||||||
unqueuedBuffers = new uint[4];
|
|
||||||
streamBufferAmplitudes = new float[4];
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
Al.GenBuffer(out streamBuffers[i]);
|
|
||||||
|
|
||||||
alError = Al.GetError();
|
|
||||||
if (alError != Al.NoError)
|
|
||||||
{
|
|
||||||
throw new Exception("Failed to generate stream buffers: " + debugName + ", " + Al.GetErrorString(alError));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Al.IsBuffer(streamBuffers[i]))
|
|
||||||
{
|
|
||||||
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid! " + debugName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Sound.Owner.InitUpdateChannelThread();
|
|
||||||
SetProperties();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -591,12 +607,6 @@ namespace Barotrauma.Sounds
|
|||||||
{
|
{
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
#endif
|
|
||||||
if (mutex != null) { Monitor.Exit(mutex); }
|
|
||||||
#if !DEBUG
|
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void SetProperties()
|
void SetProperties()
|
||||||
|
|||||||
@@ -417,6 +417,15 @@ namespace Barotrauma.Sounds
|
|||||||
return sourcePools[(int)poolIndex].ALSources[srcInd];
|
return sourcePools[(int)poolIndex].ALSources[srcInd];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the lock object for the playing channels array for a specific pool.
|
||||||
|
/// Used to protect OpenAL operations that need to be atomic with channel assignment.
|
||||||
|
/// </summary>
|
||||||
|
public object GetPlayingChannelsLock(SourcePoolIndex poolIndex)
|
||||||
|
{
|
||||||
|
return playingChannels[(int)poolIndex];
|
||||||
|
}
|
||||||
|
|
||||||
public int AssignFreeSourceToChannel(SoundChannel newChannel)
|
public int AssignFreeSourceToChannel(SoundChannel newChannel)
|
||||||
{
|
{
|
||||||
if (Disabled) { return -1; }
|
if (Disabled) { return -1; }
|
||||||
@@ -427,14 +436,25 @@ namespace Barotrauma.Sounds
|
|||||||
|
|
||||||
lock (playingChannels[poolIndex])
|
lock (playingChannels[poolIndex])
|
||||||
{
|
{
|
||||||
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
|
return AssignFreeSourceToChannelUnsafe(newChannel, poolIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Assigns a free source to a channel without locking.
|
||||||
|
/// Caller MUST hold the playingChannels[poolIndex] lock before calling this method.
|
||||||
|
/// </summary>
|
||||||
|
public int AssignFreeSourceToChannelUnsafe(SoundChannel newChannel, int poolIndex)
|
||||||
|
{
|
||||||
|
if (Disabled) { return -1; }
|
||||||
|
|
||||||
|
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
|
||||||
|
{
|
||||||
|
if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
|
||||||
{
|
{
|
||||||
if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
|
if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
|
||||||
{
|
playingChannels[poolIndex][i] = newChannel;
|
||||||
if (playingChannels[poolIndex][i] != null) { playingChannels[poolIndex][i].Dispose(); }
|
return i;
|
||||||
playingChannels[poolIndex][i] = newChannel;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,13 +496,25 @@ namespace Barotrauma.Sounds
|
|||||||
int count = 0;
|
int count = 0;
|
||||||
lock (playingChannels[(int)sound.SourcePoolIndex])
|
lock (playingChannels[(int)sound.SourcePoolIndex])
|
||||||
{
|
{
|
||||||
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
|
count = CountPlayingInstancesUnsafe(sound, (int)sound.SourcePoolIndex);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts playing instances without locking.
|
||||||
|
/// Caller MUST hold the playingChannels[poolIndex] lock before calling this method.
|
||||||
|
/// </summary>
|
||||||
|
public int CountPlayingInstancesUnsafe(Sound sound, int poolIndex)
|
||||||
|
{
|
||||||
|
if (Disabled) { return 0; }
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < playingChannels[poolIndex].Length; i++)
|
||||||
|
{
|
||||||
|
if (playingChannels[poolIndex][i] != null &&
|
||||||
|
playingChannels[poolIndex][i].Sound.Filename == sound.Filename)
|
||||||
{
|
{
|
||||||
if (playingChannels[(int)sound.SourcePoolIndex][i] != null &&
|
if (playingChannels[poolIndex][i].IsPlaying) { count++; };
|
||||||
playingChannels[(int)sound.SourcePoolIndex][i].Sound.Filename == sound.Filename)
|
|
||||||
{
|
|
||||||
if (playingChannels[(int)sound.SourcePoolIndex][i].IsPlaying) { count++; };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
public const int MaxThumbnailSize = 1024 * 1024;
|
public const int MaxThumbnailSize = 1024 * 1024;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tags the players can choose for their workshop items. These must match the ones defined in the Steamworks backend. They're case insensitive, but must otherwise match exactly for the tag filtering to work correctly.
|
||||||
|
/// The localized names for these are fetched from the loca files with the identifier "workshop.contenttag.{tag.RemoveWhitespace()}".
|
||||||
|
/// </summary>
|
||||||
public static readonly ImmutableArray<Identifier> Tags = new []
|
public static readonly ImmutableArray<Identifier> Tags = new []
|
||||||
{
|
{
|
||||||
"submarine",
|
"submarine",
|
||||||
@@ -25,7 +29,7 @@ namespace Barotrauma.Steam
|
|||||||
"monster",
|
"monster",
|
||||||
"mission",
|
"mission",
|
||||||
"outpost",
|
"outpost",
|
||||||
"beaconstation",
|
"beacon station",
|
||||||
"wreck",
|
"wreck",
|
||||||
"ruin",
|
"ruin",
|
||||||
"weapons",
|
"weapons",
|
||||||
@@ -34,14 +38,14 @@ namespace Barotrauma.Steam
|
|||||||
"art",
|
"art",
|
||||||
"event set",
|
"event set",
|
||||||
"total conversion",
|
"total conversion",
|
||||||
"gamemode",
|
"game mode",
|
||||||
"gameplaymechanics",
|
"gameplay mechanics",
|
||||||
"environment",
|
"environment",
|
||||||
"item assembly",
|
"item assembly",
|
||||||
"language",
|
"language",
|
||||||
"qol",
|
"qol",
|
||||||
"clientside",
|
"client-side",
|
||||||
"serverside",
|
"server-side",
|
||||||
"outdated",
|
"outdated",
|
||||||
"library"
|
"library"
|
||||||
}.ToIdentifiers().ToImmutableArray();
|
}.ToIdentifiers().ToImmutableArray();
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.12.6.2</Version>
|
<Version>1.12.7.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.12.6.2</Version>
|
<Version>1.12.7.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.12.6.2</Version>
|
<Version>1.12.7.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.12.6.2</Version>
|
<Version>1.12.7.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.12.6.2</Version>
|
<Version>1.12.7.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -28,11 +28,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
|
||||||
if (GameMain.Server is { ServerSettings.RespawnMode: RespawnMode.Permadeath } &&
|
if (GameMain.Server is { ServerSettings.RespawnMode: RespawnMode.Permadeath } &&
|
||||||
GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign &&
|
GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign &&
|
||||||
causeOfDeath != CauseOfDeathType.Disconnected)
|
causeOfDeath != CauseOfDeathType.Disconnected)
|
||||||
{
|
{
|
||||||
Client ownerClient = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Character == this);
|
Client ownerClient = clients.FirstOrDefault(c => c.Character == this);
|
||||||
if (ownerClient != null)
|
if (ownerClient != null)
|
||||||
{
|
{
|
||||||
ownerClient.SpectateOnly = true;
|
ownerClient.SpectateOnly = true;
|
||||||
@@ -51,7 +54,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter))
|
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)
|
if (ownerClient != null)
|
||||||
{
|
{
|
||||||
(GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient);
|
(GameMain.GameSession?.GameMode as MultiPlayerCampaign)?.SaveExperiencePoints(ownerClient);
|
||||||
@@ -62,7 +65,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (CauseOfDeath.Killer != null && CauseOfDeath.Killer.IsTraitor && CauseOfDeath.Killer != this)
|
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 (owner != null)
|
||||||
{
|
{
|
||||||
if (!LuaCsSetup.Instance.Game.overrideTraitors)
|
if (!LuaCsSetup.Instance.Game.overrideTraitors)
|
||||||
@@ -71,11 +74,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
foreach (Client client in clients)
|
||||||
{
|
{
|
||||||
if (client.InGame)
|
if (client.InGame)
|
||||||
{
|
{
|
||||||
client.PendingPositionUpdates.Enqueue(this);
|
client.TryEnqueuePositionUpdate(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -486,7 +486,9 @@ namespace Barotrauma
|
|||||||
case ControlEventData controlEventData:
|
case ControlEventData controlEventData:
|
||||||
Client owner = controlEventData.Owner;
|
Client owner = controlEventData.Owner;
|
||||||
msg.WriteBoolean(owner == c && owner.Character == this);
|
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 });
|
msg.WriteBoolean(info is { RenamingEnabled: true });
|
||||||
break;
|
break;
|
||||||
case CharacterStatusEventData statusEventData:
|
case CharacterStatusEventData statusEventData:
|
||||||
@@ -742,7 +744,9 @@ namespace Barotrauma
|
|||||||
return;
|
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)
|
if (ownerClient != null)
|
||||||
{
|
{
|
||||||
msg.WriteBoolean(true);
|
msg.WriteBoolean(true);
|
||||||
|
|||||||
@@ -2735,6 +2735,19 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
commands.Add(new Command("ShowServerPerf", "Immediately log server performance info in ServerMessage", (string[] args) =>
|
||||||
|
{
|
||||||
|
GameServer.Log(PerformanceMonitor.PM.ToString(), ServerLog.MessageType.ServerMessage);
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssignOnClientRequestExecute(
|
||||||
|
"ShowServerPerf",
|
||||||
|
(senderClient, cursorWorldPos, args) =>
|
||||||
|
{
|
||||||
|
GameMain.Server.SendConsoleMessage(PerformanceMonitor.PM.ToString(), senderClient);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) =>
|
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -82,10 +82,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets, float duration)
|
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 (targets == null || targets.None())
|
||||||
{
|
{
|
||||||
//if the action doesn't target anyone in specific, it's shown to every client
|
//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; }
|
if (IsBlockedByAnotherConversation(client, duration)) { return true; }
|
||||||
}
|
}
|
||||||
@@ -95,7 +98,7 @@ namespace Barotrauma
|
|||||||
foreach (Entity e in targets)
|
foreach (Entity e in targets)
|
||||||
{
|
{
|
||||||
if (e is not Character character || !character.IsRemotePlayer) { continue; }
|
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; }
|
if (targetClient != null && IsBlockedByAnotherConversation(targetClient, duration)) { return true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,13 +120,16 @@ namespace Barotrauma
|
|||||||
partial void ShowDialog(Character speaker, Character targetCharacter)
|
partial void ShowDialog(Character speaker, Character targetCharacter)
|
||||||
{
|
{
|
||||||
targetClients.Clear();
|
targetClients.Clear();
|
||||||
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
|
||||||
if (!TargetTag.IsEmpty)
|
if (!TargetTag.IsEmpty)
|
||||||
{
|
{
|
||||||
IEnumerable<Entity> entities = ParentEvent.GetTargets(TargetTag);
|
IEnumerable<Entity> entities = ParentEvent.GetTargets(TargetTag);
|
||||||
foreach (Entity e in entities)
|
foreach (Entity e in entities)
|
||||||
{
|
{
|
||||||
if (e is not Character character || !character.IsRemotePlayer) { continue; }
|
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)
|
if (targetClient != null)
|
||||||
{
|
{
|
||||||
targetClients.Add(targetClient);
|
targetClients.Add(targetClient);
|
||||||
@@ -135,7 +141,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
foreach (Client c in clients)
|
||||||
{
|
{
|
||||||
if (CanClientReceive(c))
|
if (CanClientReceive(c))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,48 +9,52 @@ namespace Barotrauma;
|
|||||||
|
|
||||||
partial class EventLogAction : EventAction
|
partial class EventLogAction : EventAction
|
||||||
{
|
{
|
||||||
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
|
partial void AddEntryProjSpecific(EventLog? eventLog, string displayText)
|
||||||
{
|
{
|
||||||
if (eventLog == null) { return; }
|
if (eventLog == null) { return; }
|
||||||
if (!TargetTag.IsEmpty)
|
|
||||||
{
|
|
||||||
List<Client> targetClients = new List<Client>();
|
|
||||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
|
||||||
{
|
|
||||||
if (target is Character character)
|
|
||||||
{
|
|
||||||
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
|
||||||
if (ownerClient != null)
|
|
||||||
{
|
|
||||||
targetClients.Add(ownerClient);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
|
|
||||||
ParentEvent.Prefab.ContentPackage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (eventLog!.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
|
|
||||||
{
|
|
||||||
Log(targetClients);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, GameMain.Server.ConnectedClients) && ShowInServerLog)
|
|
||||||
{
|
|
||||||
Log(targetClients: null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Log(List<Client>? targetClients)
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
|
||||||
|
if (!TargetTag.IsEmpty)
|
||||||
|
{
|
||||||
|
List<Client> targetClients = new List<Client>();
|
||||||
|
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||||
{
|
{
|
||||||
string clientStr = targetClients == null || targetClients.None() ?
|
if (target is Character character)
|
||||||
string.Empty :
|
{
|
||||||
$" ({string.Join(", ", targetClients.Select(c => NetworkMember.ClientLogName(c)))})";
|
var ownerClient = clients.FirstOrDefault(c => c.Character == character);
|
||||||
GameServer.Log($"Event \"{ParentEvent.Prefab.Name}\"{clientStr}: " + displayText,
|
if (ownerClient != null)
|
||||||
ParentEvent is TraitorEvent ? ServerLog.MessageType.Traitors : ServerLog.MessageType.Chat);
|
{
|
||||||
|
targetClients.Add(ownerClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DebugConsole.AddWarning($"{target} is not a valid target for an EventLogAction. The target should be a character.",
|
||||||
|
ParentEvent.Prefab.ContentPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eventLog!.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, targetClients) && ShowInServerLog)
|
||||||
|
{
|
||||||
|
Log(targetClients);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (eventLog.TryAddEntry(ParentEvent.Prefab.Identifier, Id, displayText, clients) && ShowInServerLog)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+7
-2
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class EventObjectiveAction : EventAction
|
partial class EventObjectiveAction : EventAction
|
||||||
@@ -13,9 +15,12 @@ namespace Barotrauma
|
|||||||
ParentObjectiveId,
|
ParentObjectiveId,
|
||||||
CanBeCompleted);
|
CanBeCompleted);
|
||||||
|
|
||||||
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
|
||||||
if (TargetTag.IsEmpty)
|
if (TargetTag.IsEmpty)
|
||||||
{
|
{
|
||||||
foreach (var client in GameMain.Server.ConnectedClients)
|
foreach (var client in clients)
|
||||||
{
|
{
|
||||||
if (client.Character == null) { continue; }
|
if (client.Character == null) { continue; }
|
||||||
EventManager.ServerWriteObjective(client, objective);
|
EventManager.ServerWriteObjective(client, objective);
|
||||||
@@ -26,7 +31,7 @@ namespace Barotrauma
|
|||||||
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
foreach (var target in ParentEvent.GetTargets(TargetTag))
|
||||||
{
|
{
|
||||||
if (target is not Character character) { continue; }
|
if (target is not Character character) { continue; }
|
||||||
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
var ownerClient = clients.FirstOrDefault(c => c.Character == character);
|
||||||
if (ownerClient == null) { continue; }
|
if (ownerClient == null) { continue; }
|
||||||
EventManager.ServerWriteObjective(ownerClient, objective);
|
EventManager.ServerWriteObjective(ownerClient, objective);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ partial class HighlightAction : EventAction
|
|||||||
IEnumerable<Client>? targetClients = null;
|
IEnumerable<Client>? targetClients = null;
|
||||||
if (targetCharacters != null)
|
if (targetCharacters != null)
|
||||||
{
|
{
|
||||||
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
targetClients = targetCharacters
|
targetClients = targetCharacters
|
||||||
.Select(c => GameMain.Server.ConnectedClients.FirstOrDefault(client => client.Character == c))
|
.Select(c => clients.FirstOrDefault(client => client.Character == c))
|
||||||
.Where(c => c != null)!;
|
.Where(c => c != null)!;
|
||||||
}
|
}
|
||||||
GameMain.Server?.CreateEntityEvent(item, new Item.SetHighlightEventData(State, highlightColor, targetClients));
|
GameMain.Server?.CreateEntityEvent(item, new Item.SetHighlightEventData(State, highlightColor, targetClients));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -22,7 +23,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private static void NotifyMissionUnlock(Mission mission)
|
private static void NotifyMissionUnlock(Mission mission)
|
||||||
{
|
{
|
||||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
foreach (Client client in clients)
|
||||||
{
|
{
|
||||||
NotifyMissionUnlock(mission, client);
|
NotifyMissionUnlock(mission, client);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ namespace Barotrauma
|
|||||||
byte selectedOption = inc.ReadByte();
|
byte selectedOption = inc.ReadByte();
|
||||||
bool isIgnore = selectedOption == byte.MaxValue;
|
bool isIgnore = selectedOption == byte.MaxValue;
|
||||||
|
|
||||||
foreach (Event ev in activeEvents)
|
foreach (Event ev in _activeEvents)
|
||||||
{
|
{
|
||||||
if (ev is not ScriptedEvent scriptedEvent) { continue; }
|
if (ev is not ScriptedEvent scriptedEvent) { continue; }
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using System.Xml.Linq;
|
|||||||
using MoonSharp.Interpreter;
|
using MoonSharp.Interpreter;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Barotrauma.LuaCs.Events;
|
using Barotrauma.LuaCs.Events;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -329,8 +330,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stopwatch performanceCounterTimer = Stopwatch.StartNew();
|
Stopwatch performanceCounterTimer = Stopwatch.StartNew();
|
||||||
|
|
||||||
stopwatch = Stopwatch.StartNew();
|
stopwatch = Stopwatch.StartNew();
|
||||||
|
|
||||||
|
PerformanceMonitor PM = new PerformanceMonitor();
|
||||||
|
|
||||||
long prevTicks = stopwatch.ElapsedTicks;
|
long prevTicks = stopwatch.ElapsedTicks;
|
||||||
while (ShouldRun)
|
while (ShouldRun)
|
||||||
{
|
{
|
||||||
@@ -380,6 +383,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Timing.Accumulator -= Timing.Step;
|
Timing.Accumulator -= Timing.Step;
|
||||||
updateCount++;
|
updateCount++;
|
||||||
|
PM.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -427,6 +431,9 @@ namespace Barotrauma
|
|||||||
updateCount = 0;
|
updateCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PerformanceMonitor.PM.Dispose();
|
||||||
|
|
||||||
stopwatch.Stop();
|
stopwatch.Stop();
|
||||||
|
|
||||||
CloseServer();
|
CloseServer();
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
var (msg, deliveryMethod) = PrepareToSend(opcode, data);
|
var (msg, deliveryMethod) = PrepareToSend(opcode, data);
|
||||||
|
|
||||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
foreach (Client client in clients)
|
||||||
{
|
{
|
||||||
if (predicate is not null && !predicate(client)) { continue; }
|
if (predicate is not null && !predicate(client)) { continue; }
|
||||||
|
|
||||||
|
|||||||
@@ -211,9 +211,9 @@ namespace Barotrauma
|
|||||||
#if DEBUG || UNSTABLE
|
#if DEBUG || UNSTABLE
|
||||||
DebugConsole.NewMessage($"Client {sender.Name} failed to put \"{item}\" in the inventory of {Owner} (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
DebugConsole.NewMessage($"Client {sender.Name} failed to put \"{item}\" in the inventory of {Owner} (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||||
#endif
|
#endif
|
||||||
if (item.body != null && !sender.PendingPositionUpdates.Contains(item))
|
if (item.body != null)
|
||||||
{
|
{
|
||||||
sender.PendingPositionUpdates.Enqueue(item);
|
sender.TryEnqueuePositionUpdate(item);
|
||||||
}
|
}
|
||||||
item.PositionUpdateInterval = 0.0f;
|
item.PositionUpdateInterval = 0.0f;
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Barotrauma.LuaCs;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -13,7 +14,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (luaPackage == null)
|
if (luaPackage == null)
|
||||||
{
|
{
|
||||||
GameMain.Server.SendChatMessage("Couldn't find the LuaCsForBarotrauma content package.", ChatMessageType.ServerMessageBox);
|
GameMain.Server.SendChatMessage("Couldn't find the ProjectEP package.", ChatMessageType.ServerMessageBox);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
catch (UnauthorizedAccessException e)
|
catch (UnauthorizedAccessException e)
|
||||||
{
|
{
|
||||||
LuaCsLogger.LogError($"Unauthorized file access exception. This usually means you already have LuaCs installed. ${e}", LuaCsMessageOrigin.LuaCs);
|
LuaCsLogger.LogError($"Unauthorized file access exception. This usually means you already have ProjectEP installed. ${e}", LuaCsMessageOrigin.LuaCs);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -62,7 +63,7 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameMain.Server.SendChatMessage("Client-Side LuaCs installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);
|
GameMain.Server.SendChatMessage("Client-Side ProjectEP installed, restart your game to apply changes.", ChatMessageType.ServerMessageBox);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
|
public bool? OnReceivedClientNetMessage(IReadMessage netMessage, ClientPacketHeader clientPacketHeader, NetworkConnection sender)
|
||||||
{
|
{
|
||||||
if (clientPacketHeader != ClientHeader)
|
if (clientPacketHeader != ClientHeader)
|
||||||
{
|
{
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender);
|
Client client = GameMain.Server.ConnectedClients.First(c => c.Connection == sender);
|
||||||
@@ -64,6 +64,8 @@ partial class NetworkingService : INetworkingService, IEventClientRawNetMessageR
|
|||||||
RequestIdSingle(netMessage, client);
|
RequestIdSingle(netMessage, client);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
|
private void HandleNetMessageId(IReadMessage netMessage, Client client = null)
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
//don't create updates if all clients are very far from the hull
|
//don't create updates if all clients are very far from the hull
|
||||||
float hullUpdateDistanceSqr = NetConfig.HullUpdateDistance * NetConfig.HullUpdateDistance;
|
float hullUpdateDistanceSqr = NetConfig.HullUpdateDistance * NetConfig.HullUpdateDistance;
|
||||||
if (!GameMain.Server.ConnectedClients.Any(c =>
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
if (!clients.Any(c =>
|
||||||
(c.Character != null && Vector2.DistanceSquared(c.Character.WorldPosition, WorldPosition) < hullUpdateDistanceSqr) ||
|
(c.Character != null && Vector2.DistanceSquared(c.Character.WorldPosition, WorldPosition) < hullUpdateDistanceSqr) ||
|
||||||
(c.SpectatePos != null && Vector2.DistanceSquared(c.SpectatePos.Value, WorldPosition) < hullUpdateDistanceSqr)) )
|
(c.SpectatePos != null && Vector2.DistanceSquared(c.SpectatePos.Value, WorldPosition) < hullUpdateDistanceSqr)) )
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -64,6 +64,32 @@ namespace Barotrauma.Networking
|
|||||||
// key = entity, value = NetTime.Now when sending
|
// key = entity, value = NetTime.Now when sending
|
||||||
public readonly Dictionary<Entity, float> PositionUpdateLastSent = new Dictionary<Entity, float>();
|
public readonly Dictionary<Entity, float> PositionUpdateLastSent = new Dictionary<Entity, float>();
|
||||||
public readonly Queue<Entity> PendingPositionUpdates = new Queue<Entity>();
|
public readonly Queue<Entity> PendingPositionUpdates = new Queue<Entity>();
|
||||||
|
private readonly HashSet<Entity> pendingPositionUpdatesSet = new HashSet<Entity>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to enqueue a position update for the given entity. Returns true if the entity was added, false if it was already in the queue.
|
||||||
|
/// Uses HashSet for O(1) lookup instead of Queue.Contains() which is O(n).
|
||||||
|
/// </summary>
|
||||||
|
public bool TryEnqueuePositionUpdate(Entity entity)
|
||||||
|
{
|
||||||
|
if (pendingPositionUpdatesSet.Add(entity))
|
||||||
|
{
|
||||||
|
PendingPositionUpdates.Enqueue(entity);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dequeues a position update and removes it from the HashSet tracking.
|
||||||
|
/// </summary>
|
||||||
|
public Entity DequeuePositionUpdate()
|
||||||
|
{
|
||||||
|
if (PendingPositionUpdates.Count == 0) { return null; }
|
||||||
|
var entity = PendingPositionUpdates.Dequeue();
|
||||||
|
pendingPositionUpdatesSet.Remove(entity);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
public bool ReadyToStart;
|
public bool ReadyToStart;
|
||||||
|
|
||||||
@@ -126,7 +152,7 @@ namespace Barotrauma.Networking
|
|||||||
if (!MathUtils.NearlyEqual(karma, syncedKarma, 10.0f))
|
if (!MathUtils.NearlyEqual(karma, syncedKarma, 10.0f))
|
||||||
{
|
{
|
||||||
syncedKarma = karma;
|
syncedKarma = karma;
|
||||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,6 +379,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
NeedsMidRoundSync = false;
|
NeedsMidRoundSync = false;
|
||||||
PendingPositionUpdates.Clear();
|
PendingPositionUpdates.Clear();
|
||||||
|
pendingPositionUpdatesSet.Clear();
|
||||||
EntityEventLastSent.Clear();
|
EntityEventLastSent.Clear();
|
||||||
LastSentEntityEventID = 0;
|
LastSentEntityEventID = 0;
|
||||||
LastRecvEntityEventID = 0;
|
LastRecvEntityEventID = 0;
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ namespace Barotrauma.Networking
|
|||||||
StartTime = DateTime.Now;
|
StartTime = DateTime.Now;
|
||||||
|
|
||||||
OnStarted(transfer);
|
OnStarted(transfer);
|
||||||
GameMain.Server.LastClientListUpdateID++;
|
GameMain.Server.IncrementLastClientListUpdateID();
|
||||||
|
|
||||||
return transfer;
|
return transfer;
|
||||||
}
|
}
|
||||||
@@ -204,7 +204,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (numRemoved > 0 || endedTransfers.Count > 0)
|
if (numRemoved > 0 || endedTransfers.Count > 0)
|
||||||
{
|
{
|
||||||
GameMain.Server.LastClientListUpdateID++;
|
GameMain.Server.IncrementLastClientListUpdateID();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -327,7 +327,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
|
|
||||||
if (newClient.Connection == OwnerConnection && OwnerConnection != null)
|
if (newClient.Connection == OwnerConnection && OwnerConnection != null)
|
||||||
{
|
{
|
||||||
@@ -742,11 +742,6 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
|
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce(
|
|
||||||
"GameServer.Update:ClientWriteFailed" + e.StackTrace.CleanupStackTrace(),
|
|
||||||
GameAnalyticsManager.ErrorSeverity.Error,
|
|
||||||
errorMsg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1150,7 +1145,9 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
KickClient(c, errorStr);
|
//Is it necessary to kick a client for a non-existing entity?
|
||||||
|
//there are plenty of things have been done if received an non-existing entity update.
|
||||||
|
//KickClient(c, errorStr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1214,18 +1211,25 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
errorLines.Add("");
|
errorLines.Add("");
|
||||||
errorLines.Add("EntitySpawner events:");
|
errorLines.Add("EntitySpawner events:");
|
||||||
foreach (var entityEvent in entityEventManager.UniqueEvents)
|
try
|
||||||
{
|
{
|
||||||
if (entityEvent.Entity is EntitySpawner)
|
foreach (var entityEvent in entityEventManager.UniqueEvents.ToList())
|
||||||
{
|
{
|
||||||
var spawnData = entityEvent.Data as EntitySpawner.SpawnOrRemove;
|
if (entityEvent.Entity is EntitySpawner)
|
||||||
errorLines.Add(
|
{
|
||||||
entityEvent.ID + ": " +
|
var spawnData = entityEvent.Data as EntitySpawner.SpawnOrRemove;
|
||||||
(spawnData is EntitySpawner.RemoveEntity ? "Remove " : "Create ") +
|
errorLines.Add(
|
||||||
spawnData.Entity.ToString() +
|
entityEvent.ID + ": " +
|
||||||
" (" + spawnData.ID + ", " + spawnData.Entity.ID + ")");
|
(spawnData is EntitySpawner.RemoveEntity ? "Remove " : "Create ") +
|
||||||
|
spawnData.Entity.ToString() +
|
||||||
|
" (" + spawnData.ID + ", " + spawnData.Entity.ID + ")");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
errorLines.Add("Failed to write EntitySpawner events.");
|
||||||
|
}
|
||||||
|
|
||||||
errorLines.Add("");
|
errorLines.Add("");
|
||||||
errorLines.Add("Last debug messages:");
|
errorLines.Add("Last debug messages:");
|
||||||
@@ -2136,7 +2140,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (lastSent > NetTime.Now - updateInterval) { continue; }
|
if (lastSent > NetTime.Now - updateInterval) { continue; }
|
||||||
}
|
}
|
||||||
if (!c.PendingPositionUpdates.Contains(otherCharacter)) { c.PendingPositionUpdates.Enqueue(otherCharacter); }
|
c.TryEnqueuePositionUpdate(otherCharacter);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Submarine sub in Submarine.Loaded)
|
foreach (Submarine sub in Submarine.Loaded)
|
||||||
@@ -2145,7 +2149,7 @@ namespace Barotrauma.Networking
|
|||||||
// (= update is only sent for the docked sub that has the smallest ID, doesn't matter if it's the main sub or a shuttle)
|
// (= update is only sent for the docked sub that has the smallest ID, doesn't matter if it's the main sub or a shuttle)
|
||||||
if (sub.Info.IsOutpost || sub.DockedTo.Any(s => s.ID < sub.ID)) { continue; }
|
if (sub.Info.IsOutpost || sub.DockedTo.Any(s => s.ID < sub.ID)) { continue; }
|
||||||
if (sub.PhysicsBody == null || sub.PhysicsBody.BodyType == FarseerPhysics.BodyType.Static) { continue; }
|
if (sub.PhysicsBody == null || sub.PhysicsBody.BodyType == FarseerPhysics.BodyType.Static) { continue; }
|
||||||
if (!c.PendingPositionUpdates.Contains(sub)) { c.PendingPositionUpdates.Enqueue(sub); }
|
c.TryEnqueuePositionUpdate(sub);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Item item in Item.ItemList)
|
foreach (Item item in Item.ItemList)
|
||||||
@@ -2162,7 +2166,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (lastSent > NetTime.Now - updateInterval) { continue; }
|
if (lastSent > NetTime.Now - updateInterval) { continue; }
|
||||||
}
|
}
|
||||||
if (!c.PendingPositionUpdates.Contains(item)) { c.PendingPositionUpdates.Enqueue(item); }
|
c.TryEnqueuePositionUpdate(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2206,7 +2210,7 @@ namespace Barotrauma.Networking
|
|||||||
entity.Removed ||
|
entity.Removed ||
|
||||||
(entity is Item item && float.IsInfinity(item.PositionUpdateInterval)))
|
(entity is Item item && float.IsInfinity(item.PositionUpdateInterval)))
|
||||||
{
|
{
|
||||||
c.PendingPositionUpdates.Dequeue();
|
c.DequeuePositionUpdate();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2228,7 +2232,7 @@ namespace Barotrauma.Networking
|
|||||||
outmsg.WritePadBits();
|
outmsg.WritePadBits();
|
||||||
|
|
||||||
c.PositionUpdateLastSent[entity] = (float)NetTime.Now;
|
c.PositionUpdateLastSent[entity] = (float)NetTime.Now;
|
||||||
c.PendingPositionUpdates.Dequeue();
|
c.DequeuePositionUpdate();
|
||||||
}
|
}
|
||||||
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
|
positionUpdateBytes = outmsg.LengthBytes - positionUpdateBytes;
|
||||||
|
|
||||||
@@ -3203,7 +3207,7 @@ namespace Barotrauma.Networking
|
|||||||
initiatedStartGame = false;
|
initiatedStartGame = false;
|
||||||
GameMain.ResetFrameTime();
|
GameMain.ResetFrameTime();
|
||||||
|
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
|
|
||||||
roundStartTime = DateTime.Now;
|
roundStartTime = DateTime.Now;
|
||||||
|
|
||||||
@@ -3502,7 +3506,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
var coolDownRemaining = Client.NameChangeCoolDown - timeSinceNameChange;
|
var coolDownRemaining = Client.NameChangeCoolDown - timeSinceNameChange;
|
||||||
SendDirectChatMessage($"ServerMessage.NameChangeFailedCooldownActive~[seconds]={(int)coolDownRemaining.TotalSeconds}", c);
|
SendDirectChatMessage($"ServerMessage.NameChangeFailedCooldownActive~[seconds]={(int)coolDownRemaining.TotalSeconds}", c);
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
//increment the ID to make sure the current server-side name is treated as the "latest",
|
//increment the ID to make sure the current server-side name is treated as the "latest",
|
||||||
//and the client correctly reverts back to the old name
|
//and the client correctly reverts back to the old name
|
||||||
c.NameId++;
|
c.NameId++;
|
||||||
@@ -3516,7 +3520,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (result != null)
|
if (result != null)
|
||||||
{
|
{
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
return result.Value;
|
return result.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3533,14 +3537,14 @@ namespace Barotrauma.Networking
|
|||||||
c.Name = newName;
|
c.Name = newName;
|
||||||
c.RejectedName = string.Empty;
|
c.RejectedName = string.Empty;
|
||||||
SendChatMessage($"ServerMessage.NameChangeSuccessful~[oldname]={oldName}~[newname]={newName}", ChatMessageType.Server);
|
SendChatMessage($"ServerMessage.NameChangeSuccessful~[oldname]={oldName}~[newname]={newName}", ChatMessageType.Server);
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//update client list even if the name cannot be changed to the one sent by the client,
|
//update client list even if the name cannot be changed to the one sent by the client,
|
||||||
//so the client will be informed what their actual name is
|
//so the client will be informed what their actual name is
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4779,7 +4783,9 @@ namespace Barotrauma.Networking
|
|||||||
public static string CharacterLogName(Character character)
|
public static string CharacterLogName(Character character)
|
||||||
{
|
{
|
||||||
if (character == null) { return "[NULL]"; }
|
if (character == null) { return "[NULL]"; }
|
||||||
Client client = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
// Create snapshot to avoid concurrent access issues during parallel updates
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
|
Client client = clients.FirstOrDefault(c => c.Character == character);
|
||||||
return ClientLogName(client, character.LogName);
|
return ClientLogName(client, character.LogName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4790,8 +4796,8 @@ namespace Barotrauma.Networking
|
|||||||
LuaCsSetup.Instance?.EventService.PublishEvent<IEventServerLog>(x => x.OnServerLog(line, messageType));
|
LuaCsSetup.Instance?.EventService.PublishEvent<IEventServerLog>(x => x.OnServerLog(line, messageType));
|
||||||
|
|
||||||
GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType);
|
GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType);
|
||||||
|
var clients = GameMain.Server.ConnectedClients.ToArray();
|
||||||
foreach (Client client in GameMain.Server.ConnectedClients)
|
foreach (Client client in clients)
|
||||||
{
|
{
|
||||||
if (!client.HasPermission(ClientPermissions.ServerLog)) continue;
|
if (!client.HasPermission(ClientPermissions.ServerLog)) continue;
|
||||||
//use sendername as the message type
|
//use sendername as the message type
|
||||||
@@ -4832,7 +4838,7 @@ namespace Barotrauma.Networking
|
|||||||
private void UpdateClientLobbies()
|
private void UpdateClientLobbies()
|
||||||
{
|
{
|
||||||
// Triggers a call to WriteClientList(), which causes clients to call GameClient.ReadClientList()
|
// Triggers a call to WriteClientList(), which causes clients to call GameClient.ReadClientList()
|
||||||
LastClientListUpdateID++;
|
IncrementLastClientListUpdateID();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Client> GetPlayingClients()
|
private List<Client> GetPlayingClients()
|
||||||
|
|||||||
@@ -163,7 +163,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
client.Character.CharacterHealth.ApplyAffliction(null, new Affliction(herpesAffliction, herpesStrength));
|
client.Character.CharacterHealth.ApplyAffliction(null, new Affliction(herpesAffliction, herpesStrength));
|
||||||
GameServer.Log($"{GameServer.ClientLogName(client)} has contracted space herpes due to low karma.", ServerLog.MessageType.Karma);
|
GameServer.Log($"{GameServer.ClientLogName(client)} has contracted space herpes due to low karma.", ServerLog.MessageType.Karma);
|
||||||
GameMain.NetworkMember.LastClientListUpdateID++;
|
GameMain.NetworkMember.IncrementLastClientListUpdateID();
|
||||||
}
|
}
|
||||||
else if (existingAffliction != null)
|
else if (existingAffliction != null)
|
||||||
{
|
{
|
||||||
|
|||||||
+197
-63
@@ -1,9 +1,16 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using static Barotrauma.EosInterface.Ownership;
|
||||||
|
|
||||||
|
|
||||||
|
// DO NOT TOUCH ANYTHING HERE
|
||||||
|
// OR EVERYTHING WILL FAIL
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
class ServerEntityEvent : NetEntityEvent
|
class ServerEntityEvent : NetEntityEvent
|
||||||
@@ -44,6 +51,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
class ServerEntityEventManager : NetEntityEventManager
|
class ServerEntityEventManager : NetEntityEventManager
|
||||||
{
|
{
|
||||||
|
static public ServerEntityEventManager SEM;
|
||||||
|
|
||||||
private readonly List<ServerEntityEvent> events;
|
private readonly List<ServerEntityEvent> events;
|
||||||
|
|
||||||
//list of unique events (i.e. !IsDuplicate) created during the round
|
//list of unique events (i.e. !IsDuplicate) created during the round
|
||||||
@@ -102,60 +111,137 @@ namespace Barotrauma.Networking
|
|||||||
private readonly GameServer server;
|
private readonly GameServer server;
|
||||||
|
|
||||||
private double lastEventCountHighWarning;
|
private double lastEventCountHighWarning;
|
||||||
|
private class PendingCreateEvent
|
||||||
|
{
|
||||||
|
public IServerSerializable Entity;
|
||||||
|
public NetEntityEvent.IData Data;
|
||||||
|
public PendingCreateEvent(IServerSerializable entity, NetEntityEvent.IData data)
|
||||||
|
{
|
||||||
|
Entity = entity;
|
||||||
|
Data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly ConcurrentQueue<PendingCreateEvent> pendingCreateQueue;
|
||||||
|
|
||||||
|
private readonly Task createEventTask;
|
||||||
|
|
||||||
|
private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
private readonly SemaphoreSlim eventSignal = new SemaphoreSlim(0);
|
||||||
|
|
||||||
public ServerEntityEventManager(GameServer server)
|
public ServerEntityEventManager(GameServer server)
|
||||||
{
|
{
|
||||||
events = new List<ServerEntityEvent>();
|
events = new List<ServerEntityEvent>();
|
||||||
|
|
||||||
this.server = server;
|
this.server = server;
|
||||||
|
|
||||||
bufferedEvents = new List<BufferedEvent>();
|
bufferedEvents = new List<BufferedEvent>();
|
||||||
|
|
||||||
uniqueEvents = new List<ServerEntityEvent>();
|
uniqueEvents = new List<ServerEntityEvent>();
|
||||||
|
pendingCreateQueue = new ConcurrentQueue<PendingCreateEvent>();
|
||||||
lastWarningTime = -10.0;
|
lastWarningTime = -10.0;
|
||||||
|
SEM = this;
|
||||||
|
|
||||||
|
createEventTask = Task.Run(() => CreateEventProcessorLoop(cancellationTokenSource.Token));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateEventProcessorLoop(CancellationToken token)
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await eventSignal.WaitAsync(100, token);
|
||||||
|
ProcessPendingCreateEvents();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessPendingCreateEvents()
|
||||||
|
{
|
||||||
|
// Dequeue and process all pending events currently in the queue.
|
||||||
|
// Use a lock to synchronize modifications to shared lists / ID.
|
||||||
|
while (pendingCreateQueue.TryDequeue(out PendingCreateEvent pending))
|
||||||
|
{
|
||||||
|
// The original CreateEvent logic (mostly unchanged) but executed under a lock
|
||||||
|
if (pending == null || pending.Entity == null) { continue; }
|
||||||
|
|
||||||
|
var entity = pending.Entity;
|
||||||
|
var extraData = pending.Data;
|
||||||
|
|
||||||
|
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
||||||
|
if (extraData != null) newEvent.SetData(extraData);
|
||||||
|
|
||||||
|
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
||||||
|
//remove old events that have been sent to all clients, they are redundant now
|
||||||
|
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
||||||
|
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
||||||
|
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
||||||
|
{
|
||||||
|
events.RemoveAll(e =>
|
||||||
|
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
||||||
|
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool duplicateFound = false;
|
||||||
|
for (int i = events.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
//we already have an identical event that's waiting to be sent
|
||||||
|
// -> no need to add a new one
|
||||||
|
if (events[i].IsDuplicate(newEvent) && !events[i].Sent)
|
||||||
|
{
|
||||||
|
duplicateFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (duplicateFound) { continue; }
|
||||||
|
|
||||||
|
ID++;
|
||||||
|
|
||||||
|
events.Add(newEvent);
|
||||||
|
|
||||||
|
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
||||||
|
{
|
||||||
|
//create a copy of the event and give it a new ID
|
||||||
|
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
||||||
|
uniqueEvent.SetData(extraData);
|
||||||
|
|
||||||
|
uniqueEvents.Add(uniqueEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
public void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData = null)
|
public void CreateEvent(IServerSerializable entity, NetEntityEvent.IData extraData = null)
|
||||||
{
|
{
|
||||||
if (!ValidateEntity(entity)) { return; }
|
if (!ValidateEntity(entity)) { return; }
|
||||||
|
|
||||||
var newEvent = new ServerEntityEvent(entity, (UInt16)(ID + 1));
|
// enqueue and let background task handle the rest
|
||||||
if (extraData != null) newEvent.SetData(extraData);
|
pendingCreateQueue.Enqueue(new PendingCreateEvent(entity, extraData));
|
||||||
|
|
||||||
bool inGameClientsPresent = server.ConnectedClients.Count(c => c.InGame) > 0;
|
if (eventSignal.CurrentCount == 0)
|
||||||
|
|
||||||
//remove old events that have been sent to all clients, they are redundant now
|
|
||||||
// keep at least one event in the list (lastSentToAll == e.ID) so we can use it to keep track of the latest ID
|
|
||||||
// and events less than 15 seconds old to give disconnected clients a bit of time to reconnect without getting desynced
|
|
||||||
if (GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration)
|
|
||||||
{
|
{
|
||||||
events.RemoveAll(e =>
|
eventSignal.Release();
|
||||||
(NetIdUtils.IdMoreRecent(lastSentToAll, e.ID) || !inGameClientsPresent) &&
|
|
||||||
e.CreateTime < Timing.TotalTime - server.ServerSettings.EventRemovalTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = events.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
//we already have an identical event that's waiting to be sent
|
|
||||||
// -> no need to add a new one
|
|
||||||
if (events[i].IsDuplicate(newEvent) && !events[i].Sent) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ID++;
|
|
||||||
|
|
||||||
events.Add(newEvent);
|
|
||||||
|
|
||||||
if (!uniqueEvents.Any(e => e.IsDuplicate(newEvent)))
|
|
||||||
{
|
|
||||||
//create a copy of the event and give it a new ID
|
|
||||||
var uniqueEvent = new ServerEntityEvent(entity, (UInt16)(uniqueEvents.Count + 1));
|
|
||||||
uniqueEvent.SetData(extraData);
|
|
||||||
|
|
||||||
uniqueEvents.Add(uniqueEvent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Cancel();
|
||||||
|
eventSignal.Release();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
createEventTask?.Wait(2000);
|
||||||
|
}
|
||||||
|
catch (AggregateException) { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
cancellationTokenSource.Dispose();
|
||||||
|
eventSignal.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Due to intensive access demend and time it takes to refactor, we use try-catch when facing thread-safety issue to skip to next update :(
|
||||||
public void Update(List<Client> clients)
|
public void Update(List<Client> clients)
|
||||||
{
|
{
|
||||||
foreach (BufferedEvent bufferedEvent in bufferedEvents)
|
foreach (BufferedEvent bufferedEvent in bufferedEvents)
|
||||||
@@ -203,26 +289,55 @@ namespace Barotrauma.Networking
|
|||||||
bufferedEvent.IsProcessed = true;
|
bufferedEvent.IsProcessed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var inGameClients = clients.FindAll(c => c.InGame && !c.NeedsMidRoundSync);
|
List<Client> inGameClients = null;
|
||||||
if (inGameClients.Count > 0)
|
List<Client> midRoundSyncClients = null;
|
||||||
{
|
Client ownerClient = null;
|
||||||
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
|
||||||
lastSentToAll = inGameClients[0].LastRecvEntityEventID;
|
|
||||||
|
|
||||||
if (server.OwnerConnection != null)
|
foreach (var c in clients)
|
||||||
|
{
|
||||||
|
if (c.InGame)
|
||||||
{
|
{
|
||||||
var owner = clients.Find(c => c.Connection == server.OwnerConnection);
|
if (c.NeedsMidRoundSync)
|
||||||
if (owner != null)
|
|
||||||
{
|
{
|
||||||
lastSentToAll = owner.LastRecvEntityEventID;
|
(midRoundSyncClients ??= new List<Client>()).Add(c);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(inGameClients ??= new List<Client>()).Add(c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inGameClients.ForEach(c =>
|
if (server.OwnerConnection != null && c.Connection == server.OwnerConnection)
|
||||||
{
|
{
|
||||||
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) { lastSentToAll = c.LastRecvEntityEventID; }
|
ownerClient = c;
|
||||||
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone)) { lastSentToAnyone = c.LastRecvEntityEventID; }
|
}
|
||||||
});
|
}
|
||||||
lastSentToAnyoneTime = events.Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
|
||||||
|
if (inGameClients != null && inGameClients.Count > 0)
|
||||||
|
{
|
||||||
|
lastSentToAnyone = inGameClients[0].LastRecvEntityEventID;
|
||||||
|
lastSentToAll = ownerClient?.LastRecvEntityEventID ?? inGameClients[0].LastRecvEntityEventID;
|
||||||
|
|
||||||
|
foreach (var c in inGameClients)
|
||||||
|
{
|
||||||
|
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID))
|
||||||
|
{
|
||||||
|
lastSentToAll = c.LastRecvEntityEventID;
|
||||||
|
}
|
||||||
|
if (NetIdUtils.IdMoreRecent(c.LastRecvEntityEventID, lastSentToAnyone))
|
||||||
|
{
|
||||||
|
lastSentToAnyone = c.LastRecvEntityEventID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
lastSentToAnyoneTime = events.ToList().Find(e => e.ID == lastSentToAnyone)?.CreateTime ?? Timing.TotalTime;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
lastSentToAnyoneTime = Timing.TotalTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
if (Timing.TotalTime - lastWarningTime > 5.0 &&
|
||||||
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
Timing.TotalTime - lastSentToAnyoneTime > 10.0 &&
|
||||||
@@ -238,7 +353,16 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
|
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
|
||||||
|
|
||||||
ServerEntityEvent firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
ServerEntityEvent firstEventToResend;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
firstEventToResend = events.Find(e => e.ID == (ushort)(lastSentToAll + 1));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
firstEventToResend = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (firstEventToResend != null &&
|
if (firstEventToResend != null &&
|
||||||
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration &&
|
GameMain.GameSession.RoundDuration > server.ServerSettings.RoundStartSyncDuration &&
|
||||||
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > server.ServerSettings.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > server.ServerSettings.OldEventKickTime))
|
((lastSentToAnyoneTime - firstEventToResend.CreateTime) > server.ServerSettings.OldReceivedEventKickTime || (Timing.TotalTime - firstEventToResend.CreateTime) > server.ServerSettings.OldEventKickTime))
|
||||||
@@ -251,15 +375,15 @@ namespace Barotrauma.Networking
|
|||||||
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
|
NetIdUtils.IdMoreRecent((UInt16)(lastSentToAll + 1), c.LastRecvEntityEventID) &&
|
||||||
(firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
(firstEventToResend.CreateTime > c.MidRoundSyncTimeOut || lastSentToAnyoneTime > c.MidRoundSyncTimeOut || Timing.TotalTime > c.MidRoundSyncTimeOut + 10.0));
|
||||||
toKick.ForEach(c =>
|
toKick.ForEach(c =>
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
DebugConsole.NewMessage(c.Name + " was kicked because they were expecting a very old network event (" + (c.LastRecvEntityEventID + 1).ToString() + ")", Color.Red);
|
||||||
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
GameServer.Log(GameServer.ClientLogName(c) + " was kicked because they were expecting a very old network event ("
|
||||||
+ (c.LastRecvEntityEventID + 1).ToString() +
|
+ (c.LastRecvEntityEventID + 1).ToString() +
|
||||||
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago, " +
|
||||||
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
(lastSentToAnyoneTime - firstEventToResend.CreateTime).ToString("0.##") + " s older than last event sent to anyone)" +
|
||||||
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
|
||||||
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.ExcessiveDesyncOldEvent));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,11 +401,21 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var timedOutClients = clients.FindAll(c => c.Connection != GameMain.Server.OwnerConnection && c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
|
if (midRoundSyncClients != null)
|
||||||
foreach (Client timedOutClient in timedOutClients)
|
|
||||||
{
|
{
|
||||||
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(timedOutClient) + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error);
|
foreach (var c in midRoundSyncClients)
|
||||||
GameMain.Server.DisconnectClient(timedOutClient, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
{
|
||||||
|
if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID))
|
||||||
|
{
|
||||||
|
lastSentToAll = (ushort)(c.FirstNewEventID - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.Connection != GameMain.Server.OwnerConnection && Timing.TotalTime > c.MidRoundSyncTimeOut)
|
||||||
|
{
|
||||||
|
GameServer.Log("Disconnecting client " + GameServer.ClientLogName(c) + ". Syncing took too long.", ServerLog.MessageType.Error);
|
||||||
|
GameMain.Server.DisconnectClient(c, PeerDisconnectPacket.WithReason(DisconnectReason.SyncTimeout));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
bufferedEvents.RemoveAll(b => b.IsProcessed);
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ namespace Barotrauma.Networking
|
|||||||
var shuttleGaps = Gap.GapList.FindAll(g => RespawnShuttles.Contains(g.Submarine) && g.ConnectedWall != null);
|
var shuttleGaps = Gap.GapList.FindAll(g => RespawnShuttles.Contains(g.Submarine) && g.ConnectedWall != null);
|
||||||
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
|
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
|
||||||
|
|
||||||
var dockingPorts = Item.ItemList.FindAll(i => RespawnShuttles.Contains(i.Submarine) && i.GetComponent<DockingPort>() != null);
|
var dockingPorts = Item.ItemList.Where(i => RespawnShuttles.Contains(i.Submarine) && i.GetComponent<DockingPort>() != null).ToList();
|
||||||
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
||||||
|
|
||||||
if (!IsShuttleInsideLevel || DateTime.Now > teamSpecificState.DespawnTime)
|
if (!IsShuttleInsideLevel || DateTime.Now > teamSpecificState.DespawnTime)
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
using Barotrauma.Networking;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
public class PerformanceMonitor
|
||||||
|
{
|
||||||
|
static public PerformanceMonitor PM;
|
||||||
|
|
||||||
|
private Stopwatch PMStopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
private double tickratetimer = 0;
|
||||||
|
|
||||||
|
private double tickrate60stimer = 0;
|
||||||
|
|
||||||
|
private static Queue<double> tickrate60s = new Queue<double>(61);
|
||||||
|
|
||||||
|
public int ItemCount
|
||||||
|
{
|
||||||
|
get{ return Item.ItemList.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CharacterCount
|
||||||
|
{
|
||||||
|
get { return Character.CharacterList.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int PhysicsBodyCount
|
||||||
|
{
|
||||||
|
get { return PhysicsBody.List.Count; }
|
||||||
|
}
|
||||||
|
public int ConnectClients
|
||||||
|
{
|
||||||
|
get { return GameMain.Server.ConnectedClients.Count; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public double RealTickRate
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long TotalTicks
|
||||||
|
{
|
||||||
|
get;set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int LastSecondTicks
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
} = 0;
|
||||||
|
|
||||||
|
public float AverageTickRate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return TotalTicks / (float)TotalTimeElapsed * 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double AverageTickRate10s
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return tickrate60s.Count > 0 ? tickrate60s.Average() : 60;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double TotalTimeElapsed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return PMStopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan TimeElapsed
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return TimeSpan.FromMilliseconds(TotalTimeElapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MemoryUsage
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Process proc = Process.GetCurrentProcess();
|
||||||
|
float memory = MathF.Round(proc.PrivateMemorySize64 / (1024 * 1024), 2);
|
||||||
|
proc.Dispose();
|
||||||
|
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public double TickRateLow
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double TickRateHigh
|
||||||
|
{
|
||||||
|
get; set;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PerformanceMonitor()
|
||||||
|
{
|
||||||
|
PM = this;
|
||||||
|
RealTickRate = 60;
|
||||||
|
TotalTicks = 0;
|
||||||
|
LastSecondTicks = 60;
|
||||||
|
TickRateLow = 60;
|
||||||
|
TickRateHigh = 60;
|
||||||
|
PMStopwatch.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
TotalTicks += 1;
|
||||||
|
LastSecondTicks += 1;
|
||||||
|
if (tickrate60s.Count > 60)
|
||||||
|
{
|
||||||
|
tickrate60s.Dequeue();
|
||||||
|
}
|
||||||
|
if (TotalTimeElapsed - 1000 >= tickratetimer)
|
||||||
|
{
|
||||||
|
RealTickRate = LastSecondTicks / (TotalTimeElapsed - tickratetimer) * 1000;
|
||||||
|
tickrate60s.Enqueue(RealTickRate);
|
||||||
|
tickratetimer = TotalTimeElapsed;
|
||||||
|
LastSecondTicks = 0;
|
||||||
|
}
|
||||||
|
if (TotalTimeElapsed - 60000 >= tickrate60stimer)
|
||||||
|
{
|
||||||
|
GameServer.Log(PM.ToString(), ServerLog.MessageType.ServerMessage);
|
||||||
|
TickRateLow = 60;
|
||||||
|
TickRateHigh = 60;
|
||||||
|
tickrate60stimer = TotalTimeElapsed;
|
||||||
|
}
|
||||||
|
if (RealTickRate > TickRateHigh)
|
||||||
|
{
|
||||||
|
TickRateHigh = RealTickRate;
|
||||||
|
}
|
||||||
|
if (RealTickRate < TickRateLow)
|
||||||
|
{
|
||||||
|
TickRateLow = RealTickRate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
PMStopwatch.Reset();
|
||||||
|
PM = null;
|
||||||
|
}
|
||||||
|
override public string ToString()
|
||||||
|
{
|
||||||
|
return $"Server Performance Info \n" +
|
||||||
|
$"Item Count: {ItemCount}\n" +
|
||||||
|
$"Character Count: {CharacterCount}\n" +
|
||||||
|
$"Clients Count {ConnectClients}\n " +
|
||||||
|
$"PhysicsBody Count: {PhysicsBodyCount}\n" +
|
||||||
|
$"Tick Rate: {RealTickRate}\n" +
|
||||||
|
$"Min Tick Rate: {TickRateLow}\n" +
|
||||||
|
$"Max Tick Rate: {TickRateHigh}\n" +
|
||||||
|
$"Total Ticks: {TotalTicks}\n" +
|
||||||
|
$"All time Average Tick Rate: {AverageTickRate}\n" +
|
||||||
|
$"60s Average Tick Rate: {AverageTickRate10s}\n" +
|
||||||
|
$"Server Run Time: {TimeElapsed}\n" +
|
||||||
|
$"Memory Usage: {MemoryUsage}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ namespace Barotrauma
|
|||||||
GameMain.ShouldRun = false;
|
GameMain.ShouldRun = false;
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
Console.WriteLine("Barotrauma Dedicated Server " + GameMain.Version +
|
Console.WriteLine("Barotrauma Dedicated Server(EP) " + GameMain.Version +
|
||||||
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
|
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
|
||||||
if (Console.IsOutputRedirected)
|
if (Console.IsOutputRedirected)
|
||||||
{
|
{
|
||||||
@@ -93,6 +93,7 @@ namespace Barotrauma
|
|||||||
private static bool hasShutDown = false;
|
private static bool hasShutDown = false;
|
||||||
private static void ShutDown()
|
private static void ShutDown()
|
||||||
{
|
{
|
||||||
|
SingleThreadWorker.Instance.Dispose();
|
||||||
if (hasShutDown) { return; }
|
if (hasShutDown) { return; }
|
||||||
hasShutDown = true;
|
hasShutDown = true;
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.12.6.2</Version>
|
<Version>1.12.7.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -10,6 +10,6 @@
|
|||||||
</Values>
|
</Values>
|
||||||
</Setting>
|
</Setting>
|
||||||
<Setting Name="UseCaching" Type="bool" Value="true" AllowChangesWhileExecuting="false"/>
|
<Setting Name="UseCaching" Type="bool" Value="true" AllowChangesWhileExecuting="false"/>
|
||||||
<Setting Name="CsRunPolicySession" Type="bool" AllowChangesWhileExecuting="false" ShowInMenus="false"/>
|
<Setting Name="IsCsEnabledForSession" Type="bool" AllowChangesWhileExecuting="false" ShowInMenus="false" Value="false"/>
|
||||||
</Settings>
|
</Settings>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
|
|||||||
@@ -35,44 +35,4 @@ if not CSActive then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if SERVER then
|
|
||||||
Networking.Receive("_luastart", function (message, client)
|
|
||||||
local num = message.ReadUInt16()
|
|
||||||
|
|
||||||
local packages = {}
|
|
||||||
|
|
||||||
for i = 1, num, 1 do
|
|
||||||
table.insert(packages, {
|
|
||||||
Name = message.ReadString(),
|
|
||||||
Version = message.ReadString(),
|
|
||||||
Id = message.ReadUInt64(),
|
|
||||||
Hash = message.ReadString()
|
|
||||||
})
|
|
||||||
end
|
|
||||||
|
|
||||||
Hook.Call("client.packages", client, packages)
|
|
||||||
end)
|
|
||||||
elseif Game.IsMultiplayer then
|
|
||||||
local message = Networking.Start("_luastart")
|
|
||||||
|
|
||||||
local packageCount = 0
|
|
||||||
for package in ContentPackageManager.EnabledPackages.All do packageCount = packageCount + 1 end
|
|
||||||
|
|
||||||
message.WriteUInt16(packageCount)
|
|
||||||
|
|
||||||
for package in ContentPackageManager.EnabledPackages.All do
|
|
||||||
local id = package.UgcId
|
|
||||||
local hash = package.Hash and package.Hash.StringRepresentation or ""
|
|
||||||
|
|
||||||
if id == nil then id = 0 end
|
|
||||||
|
|
||||||
message.WriteString(package.Name)
|
|
||||||
message.WriteString(package.ModVersion)
|
|
||||||
message.WriteUInt64(UInt64(id))
|
|
||||||
message.WriteString(hash)
|
|
||||||
end
|
|
||||||
|
|
||||||
Networking.Send(message)
|
|
||||||
end
|
|
||||||
|
|
||||||
LuaSetup = nil
|
LuaSetup = nil
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<style>
|
||||||
|
<SettingsMenuTab.LuaCsSettings color="169,212,187,255" hovercolor="220,220,220,255" selectedcolor="255,255,255,255" pressedcolor="100,100,100,255" disabledcolor="125,125,125,125">
|
||||||
|
<Sprite name="LuaCsSettings" texture="%ModDir%/LuaCsSettingsIcon.png" sourcerect="0,0,64,64" tile="false" maintainaspectratio="true" origin="0.5,0.5"/>
|
||||||
|
</SettingsMenuTab.LuaCsSettings>
|
||||||
|
</style>
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
<infotexts language="English" nowhitespace="false" translatedname="English">
|
<infotexts language="English" nowhitespace="false" translatedname="English">
|
||||||
<LuaCsForBarotrauma.SettingsMenu.ModControlsButton>Mod Controls Settings</LuaCsForBarotrauma.SettingsMenu.ModControlsButton>
|
<LuaCsForBarotrauma.SettingsMenu.ModControlsButton>Mod Controls Settings</LuaCsForBarotrauma.SettingsMenu.ModControlsButton>
|
||||||
<LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>
|
<LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>Mod Gameplay Settings</LuaCsForBarotrauma.SettingsMenu.ModGameplayButton>
|
||||||
|
<LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings>Reset Displayed Settings</LuaCsForBarotrauma.SettingsMenu.ResetVisibleSettings>
|
||||||
|
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title>Reset Visible Settings</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Title>
|
||||||
|
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message>Are you sure you want to reset the values for currently displayed settings?</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Message>
|
||||||
|
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes>Yes</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.Yes>
|
||||||
|
<LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No>No</LuaCsForBarotrauma.SettingsMenu.ResetPrompt.No>
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<!-- Is Cs Enabled-->
|
<!-- Is Cs Enabled-->
|
||||||
<LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName>
|
<LuaCsForBarotrauma.CsRunPolicy.DisplayName>Are C# Mods Allowed</LuaCsForBarotrauma.CsRunPolicy.DisplayName>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<contentpackage name="LuaCsForBarotrauma">
|
<contentpackage name="LuaCsForBarotrauma">
|
||||||
<Text file="%ModDir%/Texts/English.xml"/>
|
<Text file="%ModDir%/Texts/English.xml"/>
|
||||||
|
<UIStyle file="%ModDir%/Style.xml" />
|
||||||
<!--<Text file="%ModDir%/Texts/Portuguese.xml"/>-->
|
<!--<Text file="%ModDir%/Texts/Portuguese.xml"/>-->
|
||||||
</contentpackage>
|
</contentpackage>
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@
|
|||||||
AttachedByDefault="true" DisallowAttachingOverTags="container,planter,refuelableitem" DisallowAttachingOverSize="115,130">
|
AttachedByDefault="true" DisallowAttachingOverTags="container,planter,refuelableitem" DisallowAttachingOverSize="115,130">
|
||||||
</Holdable>
|
</Holdable>
|
||||||
|
|
||||||
<ItemContainer hideitems="false" drawinventory="true" ItemsUseInventoryPlacement="true" capacity="1" maxstacksize="1" canbeselected="true" itempos="-25,-20" iteminterval="0,0" itemrotation="0" msg="ItemMsgOxygenRefill" containedspritedepth="0.1">
|
<ItemContainer hideitems="false" drawinventory="true" ItemsUseInventoryPlacement="true" capacity="1" maxstacksize="1" canbeselected="true" itempos="32,-83" iteminterval="0,0" itemrotation="0" msg="ItemMsgOxygenRefill" containedspritedepth="0.1">
|
||||||
<GuiFrame relativesize="0.2,0.25" anchor="Center" minsize="140,170" maxsize="280,280" style="ItemUI" />
|
<GuiFrame relativesize="0.2,0.25" anchor="Center" minsize="140,170" maxsize="280,280" style="ItemUI" />
|
||||||
<SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="64,448,64,64" origin="0.5,0.5" />
|
<SlotIcon slotindex="0" texture="Content/UI/StatusMonitorUI.png" sourcerect="64,448,64,64" origin="0.5,0.5" />
|
||||||
<Containable items="oxygensource" />
|
<Containable items="oxygensource" />
|
||||||
|
|||||||
BIN
Binary file not shown.
@@ -24,29 +24,52 @@ Hook.Add("missionsEnded", "test", function()
|
|||||||
print("missionsEnded")
|
print("missionsEnded")
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- cfg tests
|
||||||
|
local str = "CLIENT: "
|
||||||
|
|
||||||
|
if SERVER then
|
||||||
|
str = "SERVER: "
|
||||||
|
end
|
||||||
|
|
||||||
|
function OnChanged(cfg)
|
||||||
|
print(str, "cfg value for ", cfg.InternalName, " changed to ", cfg.Value)
|
||||||
|
end
|
||||||
|
|
||||||
local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod")
|
local failed, package = trygetpackage("[DebugOnlyTest]TestLuaMod")
|
||||||
|
|
||||||
print("packageFailed=", failed)
|
print("packageFailed=", failed)
|
||||||
print("package", package.Name)
|
print("package", package.Name)
|
||||||
|
|
||||||
local success, config = ConfigService.TryGetConfig(SettingBase.Single, package, "TestFloat")
|
local success, config = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer")
|
||||||
|
local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient")
|
||||||
|
|
||||||
local success2, config2 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroServer")
|
if not success or not success2 then
|
||||||
local success3, config3 = ConfigService.TryGetConfig(SettingBase.Int32, package, "TestSynchroClient")
|
print("Failed to get configs.")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
print("config ", success, " ", config.Value)
|
config.OnValueChanged.add(OnChanged)
|
||||||
print("config testsynchrosrv", success2, " ", config2.Value)
|
config2.OnValueChanged.add(OnChanged)
|
||||||
print("config testsynchrocli", success3, " ", config3.Value)
|
|
||||||
|
|
||||||
local lastTime = 0
|
print(str, " testsynchroclient=", config2.Value)
|
||||||
|
print(str, " testsynchroserver=", config.Value)
|
||||||
|
|
||||||
|
-- The server should keep updating the value and it should show up on the client.
|
||||||
|
-- The client should try updating and it should fail.
|
||||||
|
|
||||||
|
local lastTime = Timer.Time + 30 -- give time to join
|
||||||
|
|
||||||
Hook.Add("think", "printconfig", function()
|
Hook.Add("think", "printconfig", function()
|
||||||
if lastTime > Timer.Time then return end
|
if lastTime > Timer.Time then return end
|
||||||
|
lastTime = Timer.Time + 10
|
||||||
|
|
||||||
lastTime = Timer.Time + 10
|
if SERVER then
|
||||||
print(config.Value)
|
local succ = config.TrySetValue(config.Value + 1)
|
||||||
|
print("Success of setting value on server for '", config.InternalName,"': ", succ)
|
||||||
|
end
|
||||||
|
if CLIENT then
|
||||||
|
local succ = config.TrySetValue(config.Value + 1)
|
||||||
|
print("Success of setting value on client for '", config.InternalName,"': ", succ, " | This should fail if permissions are not set for client.")
|
||||||
|
end
|
||||||
|
|
||||||
if SERVER then
|
|
||||||
config.TrySetValue(config.Value + 1)
|
|
||||||
end
|
|
||||||
end)
|
end)
|
||||||
|
|||||||
@@ -2,4 +2,6 @@
|
|||||||
<ModConfig>
|
<ModConfig>
|
||||||
<Lua File="%ModDir%/Lua/init.lua" IsAutorun="true" />
|
<Lua File="%ModDir%/Lua/init.lua" IsAutorun="true" />
|
||||||
<Config File="%ModDir%/Settings.xml"/>
|
<Config File="%ModDir%/Settings.xml"/>
|
||||||
|
<Config File="%ModDir%/SettingsClient.xml" Target="Client"/>
|
||||||
|
<Config File="%ModDir%/SettingsServer.xml" Target="Server"/>
|
||||||
</ModConfig>
|
</ModConfig>
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Configuration>
|
<Configuration>
|
||||||
<Settings>
|
<Settings>
|
||||||
<Setting Name="TestTickbox" Type="bool"/>
|
<Setting Name="TestTickbox" Type="bool" Value="true"/>
|
||||||
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" />
|
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="40"/>
|
||||||
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" />
|
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="25"/>
|
||||||
<Setting Name="TestFloat" Type="float" NetSync="ServerAuthority" />
|
<Setting Name="TestFloat" Type="float" Value="3498"/>
|
||||||
<Setting Name="TestHidden" Type="bool" ShowInMenus="false"/>
|
<Setting Name="TestHidden" Type="bool" ShowInMenus="false" Value="false"/>
|
||||||
<Setting Name="TestRangeFloat" Type="rangeFloat" Min="0" Max="25" Steps="11"/>
|
<Setting Name="TestRangeFloat" Type="rangeFloat" Min="0" Max="25" Steps="11" Value="4.5"/>
|
||||||
<Setting Name="TestRangeInt" Type="rangeInt" Min="0" Max="10" Steps="11"/>
|
<Setting Name="TestRangeInt" Type="rangeInt" Min="0" Max="10" Steps="11" Value="4"/>
|
||||||
<Setting Name="TestString" Type="string" />
|
<Setting Name="TestString" Type="string" Value="ok"/>
|
||||||
<Setting Name="TestControl" Type="control" Value="A"/>
|
<Setting Name="TestControl" Type="control" Value="A"/>
|
||||||
<Setting Name="TestDropdownList" Type="listString" Value="Hi">
|
<Setting Name="TestDropdownList" Type="listString" Value="Hi">
|
||||||
<Values>
|
<Values>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Configuration>
|
||||||
|
<!-- Should match 'SettingsServer'. We define these on the client and server separately to give different values. -->
|
||||||
|
<Settings>
|
||||||
|
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="3545" ShowInMenus="false"/>
|
||||||
|
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="577" ShowInMenus="false"/>
|
||||||
|
</Settings>
|
||||||
|
</Configuration>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Configuration>
|
||||||
|
<!-- Should match 'SettingsClient'. We define these on the client and server separately to give different values. -->
|
||||||
|
<Settings>
|
||||||
|
<Setting Name="TestSynchroClient" Type="int" NetSync="TwoWay" Value="40" ShowInMenus="false"/>
|
||||||
|
<Setting Name="TestSynchroServer" Type="int" NetSync="ServerAuthority" Value="25" ShowInMenus="false"/>
|
||||||
|
</Settings>
|
||||||
|
</Configuration>
|
||||||
@@ -156,7 +156,8 @@ namespace Barotrauma
|
|||||||
Reactor reactor = item.GetComponent<Reactor>();
|
Reactor reactor = item.GetComponent<Reactor>();
|
||||||
if (reactor != null && reactor.Item.Condition > 0.0f) { roundData.Reactors.Add(reactor); }
|
if (reactor != null && reactor.Item.Condition > 0.0f) { roundData.Reactors.Add(reactor); }
|
||||||
}
|
}
|
||||||
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
|
||||||
|
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||||
cachedDistances.Clear();
|
cachedDistances.Clear();
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
@@ -323,7 +324,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
static CachedDistance CalculateNewCachedDistance(Character c)
|
static CachedDistance CalculateNewCachedDistance(Character c)
|
||||||
{
|
{
|
||||||
pathFinder ??= new PathFinder(WayPoint.WayPointList, false);
|
pathFinder ??= new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition));
|
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(c.WorldPosition), ConvertUnits.ToSimUnits(Submarine.MainSub.WorldPosition));
|
||||||
if (path.Unreachable) { return null; }
|
if (path.Unreachable) { return null; }
|
||||||
return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
|
return new CachedDistance(c.WorldPosition, Submarine.MainSub.WorldPosition, path.TotalLength, Timing.TotalTime + Rand.Range(1.0f, 5.0f));
|
||||||
|
|||||||
@@ -1,13 +1,67 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Thread-safe wrapper for AITarget list operations.
|
||||||
|
/// Uses copy-on-write pattern for lock-free reads.
|
||||||
|
/// </summary>
|
||||||
|
class ThreadSafeAITargetList : IEnumerable<AITarget>
|
||||||
|
{
|
||||||
|
private volatile List<AITarget> _list = new List<AITarget>();
|
||||||
|
private readonly object _writeLock = new object();
|
||||||
|
|
||||||
|
public int Count => _list.Count;
|
||||||
|
|
||||||
|
public void Add(AITarget target)
|
||||||
|
{
|
||||||
|
lock (_writeLock)
|
||||||
|
{
|
||||||
|
var newList = new List<AITarget>(_list) { target };
|
||||||
|
Interlocked.Exchange(ref _list, newList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(AITarget target)
|
||||||
|
{
|
||||||
|
lock (_writeLock)
|
||||||
|
{
|
||||||
|
var newList = new List<AITarget>(_list);
|
||||||
|
bool removed = newList.Remove(target);
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _list, newList);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _list, new List<AITarget>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(AITarget target) => _list.Contains(target);
|
||||||
|
|
||||||
|
public AITarget this[int index] => _list[index];
|
||||||
|
|
||||||
|
public IEnumerator<AITarget> GetEnumerator() => _list.GetEnumerator();
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
public List<AITarget> ToList() => new List<AITarget>(_list);
|
||||||
|
public AITarget FirstOrDefault(Func<AITarget, bool> predicate) => _list.FirstOrDefault(predicate);
|
||||||
|
public IEnumerable<AITarget> Where(Func<AITarget, bool> predicate) => _list.Where(predicate);
|
||||||
|
public bool Any(Func<AITarget, bool> predicate) => _list.Any(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
partial class AITarget
|
partial class AITarget
|
||||||
{
|
{
|
||||||
public static List<AITarget> List = new List<AITarget>();
|
public static ThreadSafeAITargetList List = new ThreadSafeAITargetList();
|
||||||
|
|
||||||
private Entity entity;
|
private Entity entity;
|
||||||
public Entity Entity
|
public Entity Entity
|
||||||
|
|||||||
@@ -242,10 +242,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The monster won't try to damage these submarines
|
/// The monster won't try to damage these submarines. Applies to hulls, structures and static items (items without a physics body) belonging to these submarines. Does not apply to non-static items, e.g. flares or other provocative items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly HashSet<Submarine> unattackableSubmarines = new HashSet<Submarine>();
|
private readonly HashSet<Submarine> unattackableSubmarines = [];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the submarine(s) the monster won't attack. Applies to hulls, structures and static items (items without a physics body) belonging to these submarines. Does not apply to non-static items, e.g. flares or other provocative items.
|
||||||
|
/// </summary>
|
||||||
public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true)
|
public void SetUnattackableSubmarines(Submarine submarine, bool includeOwnSub = true, bool includeConnectedSubs = true, bool clearExisting = true)
|
||||||
{
|
{
|
||||||
if (clearExisting)
|
if (clearExisting)
|
||||||
@@ -3075,11 +3078,17 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Ignore all structures, items, and hulls inside these subs.
|
|
||||||
if (aiTarget.Entity.Submarine != null)
|
if (aiTarget.Entity.Submarine != null)
|
||||||
{
|
{
|
||||||
|
//ignore all items, structures and hulls in wrecks and beacon stations
|
||||||
|
//(we don't want monsters to be distracted by them during missions,
|
||||||
|
//nor have monsters inside them attack "their home" rather than the player)
|
||||||
if (aiTarget.Entity.Submarine.Info.IsWreck ||
|
if (aiTarget.Entity.Submarine.Info.IsWreck ||
|
||||||
aiTarget.Entity.Submarine.Info.IsBeacon ||
|
aiTarget.Entity.Submarine.Info.IsBeacon)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (aiTarget.Entity is Structure or Hull or Item { body: null } &&
|
||||||
unattackableSubmarines.Contains(aiTarget.Entity.Submarine))
|
unattackableSubmarines.Contains(aiTarget.Entity.Submarine))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -381,8 +382,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
|
steeringBuffer = Math.Clamp(steeringBuffer, minSteeringBuffer, maxSteeringBuffer);
|
||||||
|
|
||||||
AnimController.Crouching = shouldCrouch;
|
// in case of somehow AnimController was a null, eg. something removed AnimController in the middle of an update
|
||||||
CheckCrouching(deltaTime);
|
if (AnimController != null)
|
||||||
|
{
|
||||||
|
AnimController.Crouching = shouldCrouch;
|
||||||
|
CheckCrouching(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
Character.ClearInputs();
|
Character.ClearInputs();
|
||||||
|
|
||||||
if (SortTimer > 0.0f)
|
if (SortTimer > 0.0f)
|
||||||
@@ -1637,7 +1643,8 @@ namespace Barotrauma
|
|||||||
if (mode == AIObjectiveCombat.CombatMode.None) { return; }
|
if (mode == AIObjectiveCombat.CombatMode.None) { return; }
|
||||||
if (Character.IsDead || Character.IsIncapacitated || Character.Removed) { return; }
|
if (Character.IsDead || Character.IsIncapacitated || Character.Removed) { return; }
|
||||||
if (!Character.IsBot) { return; }
|
if (!Character.IsBot) { return; }
|
||||||
if (ObjectiveManager.Objectives.FirstOrDefault(o => o is AIObjectiveCombat) is AIObjectiveCombat combatObjective)
|
List<AIObjective> ObjectivesLocal = ObjectiveManager.Objectives;
|
||||||
|
if (ObjectivesLocal.FirstOrDefault(o => o is AIObjectiveCombat) is AIObjectiveCombat combatObjective)
|
||||||
{
|
{
|
||||||
// Don't replace offensive mode with something else
|
// Don't replace offensive mode with something else
|
||||||
if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; }
|
if (combatObjective.Mode == AIObjectiveCombat.CombatMode.Offensive && mode != AIObjectiveCombat.CombatMode.Offensive) { return; }
|
||||||
@@ -1817,7 +1824,9 @@ namespace Barotrauma
|
|||||||
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
public static bool HasDivingMask(Character character, float conditionPercentage = 0, bool requireOxygenTank = true)
|
||||||
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
=> HasItem(character, Tags.LightDivingGear, out _, requireOxygenTank ? Tags.OxygenSource : Identifier.Empty, conditionPercentage, requireEquipped: true);
|
||||||
|
|
||||||
private static List<Item> matchingItems = new List<Item>();
|
// ThreadLocal to ensure thread safety - each thread gets its own list instance
|
||||||
|
private static readonly ThreadLocal<List<Item>> matchingItemsLocal = new ThreadLocal<List<Item>>(() => new List<Item>());
|
||||||
|
private static List<Item> matchingItems => matchingItemsLocal.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Note: uses a single list for matching items. The item is reused each time when the method is called. So if you use the method twice, and then refer to the first items, you'll actually get the second.
|
/// Note: uses a single list for matching items. The item is reused each time when the method is called. So if you use the method twice, and then refer to the first items, you'll actually get the second.
|
||||||
@@ -1825,15 +1834,16 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable<Item> items, Identifier containedTag = default, float conditionPercentage = 0, bool requireEquipped = false, bool recursive = true, Func<Item, bool> predicate = null)
|
public static bool HasItem(Character character, Identifier tagOrIdentifier, out IEnumerable<Item> items, Identifier containedTag = default, float conditionPercentage = 0, bool requireEquipped = false, bool recursive = true, Func<Item, bool> predicate = null)
|
||||||
{
|
{
|
||||||
matchingItems.Clear();
|
var localMatchingItems = matchingItems;
|
||||||
items = matchingItems;
|
localMatchingItems.Clear();
|
||||||
|
items = localMatchingItems;
|
||||||
if (character?.Inventory == null) { return false; }
|
if (character?.Inventory == null) { return false; }
|
||||||
matchingItems = character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
character.Inventory.FindAllItems(i => (i.Prefab.Identifier == tagOrIdentifier || i.HasTag(tagOrIdentifier)) &&
|
||||||
i.ConditionPercentage >= conditionPercentage &&
|
i.ConditionPercentage >= conditionPercentage &&
|
||||||
(!requireEquipped || character.HasEquippedItem(i)) &&
|
(!requireEquipped || character.HasEquippedItem(i)) &&
|
||||||
(predicate == null || predicate(i)), recursive, matchingItems);
|
(predicate == null || predicate(i)), recursive, localMatchingItems);
|
||||||
items = matchingItems;
|
items = localMatchingItems;
|
||||||
foreach (var item in matchingItems)
|
foreach (var item in localMatchingItems)
|
||||||
{
|
{
|
||||||
if (item == null) { continue; }
|
if (item == null) { continue; }
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -9,7 +10,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
class NPCConversationCollection : Prefab
|
class NPCConversationCollection : Prefab
|
||||||
{
|
{
|
||||||
public static readonly Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new Dictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
// Thread-safe dictionary for language-based collections
|
||||||
|
public static readonly ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>> Collections = new ConcurrentDictionary<LanguageIdentifier, PrefabCollection<NPCConversationCollection>>();
|
||||||
|
|
||||||
public readonly LanguageIdentifier Language;
|
public readonly LanguageIdentifier Language;
|
||||||
|
|
||||||
@@ -160,7 +162,24 @@ namespace Barotrauma
|
|||||||
return currentFlags;
|
return currentFlags;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly List<NPCConversation> previousConversations = new List<NPCConversation>();
|
// Thread-safe previous conversations tracking using copy-on-write pattern
|
||||||
|
private static volatile List<NPCConversation> _previousConversations = new List<NPCConversation>();
|
||||||
|
private static readonly object _previousConversationsLock = new object();
|
||||||
|
private static List<NPCConversation> previousConversations => _previousConversations;
|
||||||
|
|
||||||
|
private static void AddToPreviousConversations(NPCConversation conversation)
|
||||||
|
{
|
||||||
|
lock (_previousConversationsLock)
|
||||||
|
{
|
||||||
|
var newList = new List<NPCConversation>(_previousConversations);
|
||||||
|
newList.Insert(0, conversation);
|
||||||
|
if (newList.Count > MaxPreviousConversations)
|
||||||
|
{
|
||||||
|
newList.RemoveAt(MaxPreviousConversations);
|
||||||
|
}
|
||||||
|
_previousConversations = newList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static List<(Character speaker, string line)> CreateRandom(List<Character> availableSpeakers)
|
public static List<(Character speaker, string line)> CreateRandom(List<Character> availableSpeakers)
|
||||||
{
|
{
|
||||||
@@ -281,8 +300,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (baseConversation == null)
|
if (baseConversation == null)
|
||||||
{
|
{
|
||||||
previousConversations.Insert(0, selectedConversation);
|
AddToPreviousConversations(selectedConversation);
|
||||||
if (previousConversations.Count > MaxPreviousConversations) previousConversations.RemoveAt(MaxPreviousConversations);
|
|
||||||
}
|
}
|
||||||
lineList.Add((speaker, selectedConversation.Line));
|
lineList.Add((speaker, selectedConversation.Line));
|
||||||
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
CreateConversation(availableSpeakers, assignedSpeakers, selectedConversation, lineList, availableConversations);
|
||||||
|
|||||||
+1
-1
@@ -119,7 +119,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override bool CheckObjectiveState()
|
protected override bool CheckObjectiveState()
|
||||||
{
|
{
|
||||||
if (item.IgnoreByAI(character) || Item.DeconstructItems.Contains(item))
|
if (item.IgnoreByAI(character) || Item.IsMarkedForDeconstruction(item))
|
||||||
{
|
{
|
||||||
Abandon = true;
|
Abandon = true;
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -114,7 +114,7 @@ namespace Barotrauma
|
|||||||
if (!allowUnloading) { return false; }
|
if (!allowUnloading) { return false; }
|
||||||
if (requireValidContainer && !IsValidContainer(item.Container, character)) { return false; }
|
if (requireValidContainer && !IsValidContainer(item.Container, character)) { return false; }
|
||||||
}
|
}
|
||||||
if (ignoreItemsMarkedForDeconstruction && Item.DeconstructItems.Contains(item)) { return false; }
|
if (ignoreItemsMarkedForDeconstruction && Item.IsMarkedForDeconstruction(item)) { return false; }
|
||||||
if (!item.HasAccess(character)) { return false; }
|
if (!item.HasAccess(character)) { return false; }
|
||||||
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
if (character != null && !IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||||
if (item.HasBallastFloraInHull) { return false; }
|
if (item.HasBallastFloraInHull) { return false; }
|
||||||
|
|||||||
+3
-1
@@ -1,5 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
@@ -68,8 +69,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When did the character last inspect whether some other character has stolen items on them?
|
/// When did the character last inspect whether some other character has stolen items on them?
|
||||||
|
/// Thread-safe dictionary for concurrent access.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<Character, double> lastInspectionTimes = new Dictionary<Character, double>();
|
private static readonly ConcurrentDictionary<Character, double> lastInspectionTimes = new ConcurrentDictionary<Character, double>();
|
||||||
|
|
||||||
private const float NormalInspectionInterval = 120.0f;
|
private const float NormalInspectionInterval = 120.0f;
|
||||||
private const float CriminalInspectionInterval = 30.0f;
|
private const float CriminalInspectionInterval = 30.0f;
|
||||||
|
|||||||
+1
-1
@@ -122,7 +122,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Objectives.RemoveAll(o => o.GetType() == type);
|
Objectives.RemoveAll(o => o?.GetType() == type);
|
||||||
}
|
}
|
||||||
Objectives.Add(objective);
|
Objectives.Add(objective);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
if (Identifier == Tags.DeconstructThis && item.AllowDeconstruct)
|
||||||
{
|
{
|
||||||
if (item.AllowDeconstruct && !Item.DeconstructItems.Contains(item) &&
|
if (item.AllowDeconstruct && !Item.IsMarkedForDeconstruction(item) &&
|
||||||
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
|
//only allow deconstructing if there are no deconstruction recipes (= deconstructing yields nothing), or deconstruction recipes that
|
||||||
(item.Prefab.DeconstructItems.None() ||
|
(item.Prefab.DeconstructItems.None() ||
|
||||||
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
item.Prefab.DeconstructItems.Any(deconstructItem =>
|
||||||
@@ -454,7 +454,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (Identifier == Tags.DontDeconstructThis)
|
else if (Identifier == Tags.DontDeconstructThis)
|
||||||
{
|
{
|
||||||
if (Item.DeconstructItems.Contains(item)) { return true; }
|
if (Item.IsMarkedForDeconstruction(item)) { return true; }
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
ImmutableArray<Identifier> targetItems = GetTargetItems(option);
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ using FarseerPhysics.Dynamics.Joints;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MoonSharp.Interpreter;
|
using MoonSharp.Interpreter;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using JointParams = Barotrauma.RagdollParams.JointParams;
|
using JointParams = Barotrauma.RagdollParams.JointParams;
|
||||||
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
using LimbParams = Barotrauma.RagdollParams.LimbParams;
|
||||||
@@ -26,7 +28,33 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
const float MaxImpactDamage = 0.1f;
|
const float MaxImpactDamage = 0.1f;
|
||||||
|
|
||||||
private static readonly List<Ragdoll> list = new List<Ragdoll>();
|
// Thread-safe list using copy-on-write pattern (ConcurrentBag doesn't support indexer/Remove)
|
||||||
|
private static volatile List<Ragdoll> _list = new List<Ragdoll>();
|
||||||
|
private static readonly object _listLock = new object();
|
||||||
|
private static List<Ragdoll> list => _list;
|
||||||
|
|
||||||
|
private static void ListAdd(Ragdoll ragdoll)
|
||||||
|
{
|
||||||
|
lock (_listLock)
|
||||||
|
{
|
||||||
|
var newList = new List<Ragdoll>(_list) { ragdoll };
|
||||||
|
Interlocked.Exchange(ref _list, newList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ListRemove(Ragdoll ragdoll)
|
||||||
|
{
|
||||||
|
lock (_listLock)
|
||||||
|
{
|
||||||
|
var newList = new List<Ragdoll>(_list);
|
||||||
|
bool removed = newList.Remove(ragdoll);
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _list, newList);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Impact
|
struct Impact
|
||||||
{
|
{
|
||||||
@@ -47,7 +75,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Queue<Impact> impactQueue = new Queue<Impact>();
|
// Thread-safe queue for physics collision callbacks
|
||||||
|
private readonly ConcurrentQueue<Impact> impactQueue = new ConcurrentQueue<Impact>();
|
||||||
|
|
||||||
protected Hull currentHull;
|
protected Hull currentHull;
|
||||||
|
|
||||||
@@ -469,7 +498,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
|
public Ragdoll(Character character, string seed, RagdollParams ragdollParams = null)
|
||||||
{
|
{
|
||||||
list.Add(this);
|
ListAdd(this);
|
||||||
this.character = character;
|
this.character = character;
|
||||||
Recreate(ragdollParams ?? RagdollParams);
|
Recreate(ragdollParams ?? RagdollParams);
|
||||||
}
|
}
|
||||||
@@ -746,10 +775,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!f2.IsSensor)
|
if (!f2.IsSensor)
|
||||||
{
|
{
|
||||||
lock (impactQueue)
|
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||||
{
|
|
||||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -821,10 +847,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (impactQueue)
|
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
||||||
{
|
|
||||||
impactQueue.Enqueue(new Impact(f1, f2, contact, velocity));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1193,6 +1216,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles = true)
|
public void Teleport(Vector2 moveAmount, Vector2 velocityChange, bool detachProjectiles = true)
|
||||||
{
|
{
|
||||||
|
// Hopefully this will fix some crashes :(
|
||||||
|
// If Collider was null then no need to procced: nothing is there already
|
||||||
|
if (Collider == null) { return; }
|
||||||
|
|
||||||
foreach (Limb limb in Limbs)
|
foreach (Limb limb in Limbs)
|
||||||
{
|
{
|
||||||
if (limb.IsSevered) { continue; }
|
if (limb.IsSevered) { continue; }
|
||||||
@@ -1214,6 +1241,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
character.DisableImpactDamageTimer = 0.25f;
|
character.DisableImpactDamageTimer = 0.25f;
|
||||||
|
|
||||||
|
// Why they did null check below but didn't do it here????
|
||||||
SetPosition(Collider.SimPosition + moveAmount);
|
SetPosition(Collider.SimPosition + moveAmount);
|
||||||
character.CursorPosition += moveAmount;
|
character.CursorPosition += moveAmount;
|
||||||
|
|
||||||
@@ -1286,10 +1314,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
|
if (!character.Enabled || character.Removed || Frozen || Invalid || Collider == null || Collider.Removed) { return; }
|
||||||
|
|
||||||
while (impactQueue.Count > 0)
|
while (impactQueue.TryDequeue(out var impact))
|
||||||
{
|
{
|
||||||
var impact = impactQueue.Dequeue();
|
ApplyImpact(impact.F1, impact.F2, impact.LocalNormal, impact.ImpactPos, impact.Velocity);
|
||||||
ApplyImpact(impact.F1, impact.F2, impact.WorldNormal, impact.ImpactPos, impact.Velocity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckValidity();
|
CheckValidity();
|
||||||
@@ -2235,8 +2262,9 @@ namespace Barotrauma
|
|||||||
if (limb == null)
|
if (limb == null)
|
||||||
{
|
{
|
||||||
// Didn't seek or find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
|
// Didn't seek or find a (valid) limb of the matching type. If there's multiple limbs of the same type, check the other limbs.
|
||||||
foreach (var l in limbs)
|
foreach (var l in Limbs)
|
||||||
{
|
{
|
||||||
|
if (l == null) { continue; }
|
||||||
if (l.Removed) { continue; }
|
if (l.Removed) { continue; }
|
||||||
if (useSecondaryType)
|
if (useSecondaryType)
|
||||||
{
|
{
|
||||||
@@ -2362,7 +2390,7 @@ namespace Barotrauma
|
|||||||
LimbJoints = null;
|
LimbJoints = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Remove(this);
|
ListRemove(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RemoveAll()
|
public static void RemoveAll()
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ using FarseerPhysics;
|
|||||||
using FarseerPhysics.Dynamics;
|
using FarseerPhysics.Dynamics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
#if SERVER
|
#if SERVER
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -28,12 +30,70 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
|
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Thread-safe wrapper for character list operations.
|
||||||
|
/// Provides lock-free read operations and synchronized write operations.
|
||||||
|
/// </summary>
|
||||||
|
class ThreadSafeCharacterList : IEnumerable<Character>
|
||||||
|
{
|
||||||
|
private volatile List<Character> _list = new List<Character>();
|
||||||
|
private readonly object _writeLock = new object();
|
||||||
|
|
||||||
|
public int Count => _list.Count;
|
||||||
|
|
||||||
|
public void Add(Character character)
|
||||||
|
{
|
||||||
|
lock (_writeLock)
|
||||||
|
{
|
||||||
|
var newList = new List<Character>(_list) { character };
|
||||||
|
Interlocked.Exchange(ref _list, newList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Remove(Character character)
|
||||||
|
{
|
||||||
|
lock (_writeLock)
|
||||||
|
{
|
||||||
|
var newList = new List<Character>(_list);
|
||||||
|
bool removed = newList.Remove(character);
|
||||||
|
if (removed)
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _list, newList);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
Interlocked.Exchange(ref _list, new List<Character>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Contains(Character character) => _list.Contains(character);
|
||||||
|
|
||||||
|
public Character this[int index] => _list[index];
|
||||||
|
|
||||||
|
public IEnumerator<Character> GetEnumerator() => _list.GetEnumerator();
|
||||||
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
|
||||||
|
// LINQ-friendly snapshot for complex queries
|
||||||
|
public List<Character> ToList() => new List<Character>(_list);
|
||||||
|
|
||||||
|
public Character FirstOrDefault(Func<Character, bool> predicate) => _list.FirstOrDefault(predicate);
|
||||||
|
public Character Find(Predicate<Character> predicate) => _list.Find(predicate);
|
||||||
|
public List<Character> FindAll(Predicate<Character> predicate) => _list.FindAll(predicate);
|
||||||
|
public IEnumerable<Character> Where(Func<Character, bool> predicate) => _list.Where(predicate);
|
||||||
|
public bool Any(Func<Character, bool> predicate) => _list.Any(predicate);
|
||||||
|
public bool None(Func<Character, bool> predicate) => !_list.Any(predicate);
|
||||||
|
public int CountWhere(Func<Character, bool> predicate) => _list.Count(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||||
{
|
{
|
||||||
public static readonly List<Character> CharacterList = new List<Character>();
|
public static readonly ThreadSafeCharacterList CharacterList = new ThreadSafeCharacterList();
|
||||||
|
|
||||||
public static int CharacterUpdateInterval = 1;
|
public static int CharacterUpdateInterval = 1;
|
||||||
private static int characterUpdateTick = 1;
|
private static volatile int characterUpdateTick = 1;
|
||||||
|
|
||||||
public const float MaxHighlightDistance = 150.0f;
|
public const float MaxHighlightDistance = 150.0f;
|
||||||
public const float MaxDragDistance = 200.0f;
|
public const float MaxDragDistance = 200.0f;
|
||||||
@@ -2793,10 +2853,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
int itemsPerFrame = IsOnPlayerTeam ? 100 : 10;
|
int itemsPerFrame = IsOnPlayerTeam ? 100 : 10;
|
||||||
int checkedItemCount = 0;
|
int checkedItemCount = 0;
|
||||||
for (int i = 0; i < itemsPerFrame && itemIndex < Item.ItemList.Count; i++, itemIndex++)
|
var cachedItems = Item.GetCachedItemList();
|
||||||
|
for (int i = 0; i < itemsPerFrame && itemIndex < cachedItems.Count; i++, itemIndex++)
|
||||||
{
|
{
|
||||||
checkedItemCount++;
|
checkedItemCount++;
|
||||||
var item = Item.ItemList[itemIndex];
|
var item = cachedItems[itemIndex];
|
||||||
if (!item.IsInteractable(this)) { continue; }
|
if (!item.IsInteractable(this)) { continue; }
|
||||||
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
|
if (ignoredItems != null && ignoredItems.Contains(item)) { continue; }
|
||||||
if (item.Submarine == null) { continue; }
|
if (item.Submarine == null) { continue; }
|
||||||
@@ -2832,10 +2893,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetItem = _foundItem;
|
targetItem = _foundItem;
|
||||||
bool completed = itemIndex >= Item.ItemList.Count - 1;
|
bool completed = itemIndex >= cachedItems.Count - 1;
|
||||||
if (HumanAIController.DebugAI && checkedItemCount > 0 && targetItem != null && StopWatch.ElapsedMilliseconds > 1)
|
if (HumanAIController.DebugAI && checkedItemCount > 0 && targetItem != null && StopWatch.ElapsedMilliseconds > 1)
|
||||||
{
|
{
|
||||||
var msg = $"Went through {checkedItemCount} of total {Item.ItemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
|
var msg = $"Went through {checkedItemCount} of total {cachedItems.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {completed}";
|
||||||
if (StopWatch.ElapsedMilliseconds > 5)
|
if (StopWatch.ElapsedMilliseconds > 5)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError(msg);
|
DebugConsole.ThrowError(msg);
|
||||||
@@ -3432,21 +3493,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
characterUpdateTick++;
|
characterUpdateTick++;
|
||||||
|
|
||||||
if (characterUpdateTick % CharacterUpdateInterval == 0)
|
for (int i = 0; i < CharacterList.Count; i++)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < CharacterList.Count; i++)
|
CharacterList[i].Update(deltaTime, cam);
|
||||||
{
|
|
||||||
if (LuaCsSetup.Instance.Game.UpdatePriorityCharacters.Contains(CharacterList[i])) continue;
|
|
||||||
|
|
||||||
CharacterList[i].Update(deltaTime * CharacterUpdateInterval, cam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Character character in LuaCsSetup.Instance.Game.UpdatePriorityCharacters)
|
|
||||||
{
|
|
||||||
if (character.Removed) { continue; }
|
|
||||||
Debug.Assert(character is { Removed: false });
|
|
||||||
character.Update(deltaTime, cam);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
@@ -4922,7 +4971,11 @@ namespace Barotrauma
|
|||||||
HealthUpdateInterval = 0.0f;
|
HealthUpdateInterval = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<ISerializableEntity> t_statusEffectTargets;
|
||||||
|
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
||||||
|
|
||||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||||
{
|
{
|
||||||
if (actionType == ActionType.OnEating)
|
if (actionType == ActionType.OnEating)
|
||||||
@@ -4951,6 +5004,7 @@ namespace Barotrauma
|
|||||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||||
{
|
{
|
||||||
|
var targets = StatusEffectTargets;
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||||
statusEffect.Apply(actionType, deltaTime, this, targets);
|
statusEffect.Apply(actionType, deltaTime, this, targets);
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ using Barotrauma.Networking;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MoonSharp.Interpreter;
|
using MoonSharp.Interpreter;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using static OneOf.Types.TrueFalseOrNull;
|
using MoonSharp.Interpreter;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -134,8 +136,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
|
private readonly List<LimbHealth> limbHealths = new List<LimbHealth>();
|
||||||
|
|
||||||
private readonly Dictionary<Affliction, LimbHealth> afflictions = new Dictionary<Affliction, LimbHealth>();
|
// Thread-safe afflictions dictionary for concurrent access
|
||||||
private readonly HashSet<Affliction> irremovableAfflictions = new HashSet<Affliction>();
|
private readonly ConcurrentDictionary<Affliction, LimbHealth> afflictions = new ConcurrentDictionary<Affliction, LimbHealth>();
|
||||||
|
private readonly ConcurrentDictionary<Affliction, byte> irremovableAfflictions = new ConcurrentDictionary<Affliction, byte>();
|
||||||
private Affliction bloodlossAffliction;
|
private Affliction bloodlossAffliction;
|
||||||
private Affliction oxygenLowAffliction;
|
private Affliction oxygenLowAffliction;
|
||||||
private Affliction pressureAffliction;
|
private Affliction pressureAffliction;
|
||||||
@@ -326,13 +329,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void InitIrremovableAfflictions()
|
private void InitIrremovableAfflictions()
|
||||||
{
|
{
|
||||||
irremovableAfflictions.Add(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f));
|
irremovableAfflictions.TryAdd(bloodlossAffliction = new Affliction(AfflictionPrefab.Bloodloss, 0.0f), 0);
|
||||||
irremovableAfflictions.Add(stunAffliction = new Affliction(AfflictionPrefab.Stun, 0.0f));
|
irremovableAfflictions.TryAdd(stunAffliction = new Affliction(AfflictionPrefab.Stun, 0.0f), 0);
|
||||||
irremovableAfflictions.Add(pressureAffliction = new Affliction(AfflictionPrefab.Pressure, 0.0f));
|
irremovableAfflictions.TryAdd(pressureAffliction = new Affliction(AfflictionPrefab.Pressure, 0.0f), 0);
|
||||||
irremovableAfflictions.Add(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f));
|
irremovableAfflictions.TryAdd(oxygenLowAffliction = new Affliction(AfflictionPrefab.OxygenLow, 0.0f), 0);
|
||||||
foreach (Affliction affliction in irremovableAfflictions)
|
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
||||||
{
|
{
|
||||||
afflictions.Add(affliction, null);
|
afflictions.TryAdd(affliction, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,7 +343,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public IReadOnlyCollection<Affliction> GetAllAfflictions()
|
public IReadOnlyCollection<Affliction> GetAllAfflictions()
|
||||||
{
|
{
|
||||||
return afflictions.Keys;
|
return afflictions.Keys.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<Affliction> GetAllAfflictions(Func<Affliction, bool> limbHealthFilter)
|
public IEnumerable<Affliction> GetAllAfflictions(Func<Affliction, bool> limbHealthFilter)
|
||||||
@@ -505,6 +508,7 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
public float GetResistance(AfflictionPrefab afflictionPrefab, LimbType limbType)
|
||||||
{
|
{
|
||||||
|
// ConcurrentDictionary is thread-safe, no lock needed
|
||||||
// This is a % resistance (0 to 1.0)
|
// This is a % resistance (0 to 1.0)
|
||||||
float resistance = 0.0f;
|
float resistance = 0.0f;
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
@@ -539,20 +543,25 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Affliction> matchingAfflictions = new List<Affliction>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_matchingAfflictions;
|
||||||
|
private static List<Affliction> MatchingAfflictions => t_matchingAfflictions ??= new List<Affliction>();
|
||||||
|
|
||||||
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
public void ReduceAllAfflictionsOnAllLimbs(float amount, ActionType? treatmentAction = null)
|
||||||
{
|
{
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
matchingAfflictions.AddRange(afflictions.Keys);
|
matchingAfflictions.AddRange(afflictions.Keys);
|
||||||
|
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
public void ReduceAfflictionOnAllLimbs(Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||||
{
|
{
|
||||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||||
|
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
{
|
{
|
||||||
@@ -562,7 +571,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
private IEnumerable<Affliction> GetAfflictionsForLimb(Limb targetLimb)
|
||||||
@@ -572,10 +581,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||||
|
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
matchingAfflictions.AddRange(GetAfflictionsForLimb(targetLimb));
|
||||||
|
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
public void ReduceAfflictionOnLimb(Limb targetLimb, Identifier afflictionIdOrType, float amount, ActionType? treatmentAction = null, Character attacker = null)
|
||||||
@@ -583,6 +593,7 @@ namespace Barotrauma
|
|||||||
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
if (afflictionIdOrType.IsEmpty) { throw new ArgumentException($"{nameof(afflictionIdOrType)} is empty"); }
|
||||||
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
if (targetLimb is null) { throw new ArgumentNullException(nameof(targetLimb)); }
|
||||||
|
|
||||||
|
var matchingAfflictions = MatchingAfflictions;
|
||||||
matchingAfflictions.Clear();
|
matchingAfflictions.Clear();
|
||||||
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
var targetLimbHealth = limbHealths[targetLimb.HealthIndex];
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
@@ -593,10 +604,10 @@ namespace Barotrauma
|
|||||||
matchingAfflictions.Add(affliction.Key);
|
matchingAfflictions.Add(affliction.Key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReduceMatchingAfflictions(amount, treatmentAction, attacker);
|
ReduceMatchingAfflictions(matchingAfflictions, amount, treatmentAction, attacker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReduceMatchingAfflictions(float amount, ActionType? treatmentAction, Character attacker = null)
|
private void ReduceMatchingAfflictions(List<Affliction> matchingAfflictions, float amount, ActionType? treatmentAction, Character attacker = null)
|
||||||
{
|
{
|
||||||
if (matchingAfflictions.Count == 0) { return; }
|
if (matchingAfflictions.Count == 0) { return; }
|
||||||
|
|
||||||
@@ -684,12 +695,19 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly static List<Affliction> afflictionsToRemove = new List<Affliction>();
|
// Thread-static to avoid concurrent modification when multiple characters are updated in parallel
|
||||||
private readonly static List<KeyValuePair<Affliction, LimbHealth>> afflictionsToUpdate = new List<KeyValuePair<Affliction, LimbHealth>>();
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_afflictionsToRemove;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<KeyValuePair<Affliction, LimbHealth>> t_afflictionsToUpdate;
|
||||||
|
private static List<Affliction> AfflictionsToRemove => t_afflictionsToRemove ??= new List<Affliction>();
|
||||||
|
private static List<KeyValuePair<Affliction, LimbHealth>> AfflictionsToUpdate => t_afflictionsToUpdate ??= new List<KeyValuePair<Affliction, LimbHealth>>();
|
||||||
|
|
||||||
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
public void SetAllDamage(float damageAmount, float bleedingDamageAmount, float burnDamageAmount)
|
||||||
{
|
{
|
||||||
if (Unkillable || Character.GodMode) { return; }
|
if (Unkillable || Character.GodMode) { return; }
|
||||||
|
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||||
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
a.Prefab.AfflictionType == AfflictionPrefab.InternalDamage.AfflictionType ||
|
||||||
@@ -697,14 +715,14 @@ namespace Barotrauma
|
|||||||
a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType));
|
a.Prefab.AfflictionType == AfflictionPrefab.Bleeding.AfflictionType));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.Remove(affliction);
|
afflictions.TryRemove(affliction, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (LimbHealth limbHealth in limbHealths)
|
foreach (LimbHealth limbHealth in limbHealths)
|
||||||
{
|
{
|
||||||
if (damageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); }
|
if (damageAmount > 0.0f) { afflictions.TryAdd(AfflictionPrefab.InternalDamage.Instantiate(damageAmount), limbHealth); }
|
||||||
if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.Add(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); }
|
if (bleedingDamageAmount > 0.0f && DoesBleed) { afflictions.TryAdd(AfflictionPrefab.Bleeding.Instantiate(bleedingDamageAmount), limbHealth); }
|
||||||
if (burnDamageAmount > 0.0f) { afflictions.Add(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); }
|
if (burnDamageAmount > 0.0f) { afflictions.TryAdd(AfflictionPrefab.Burn.Instantiate(burnDamageAmount), limbHealth); }
|
||||||
}
|
}
|
||||||
|
|
||||||
RecalculateVitality();
|
RecalculateVitality();
|
||||||
@@ -740,26 +758,28 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
public void RemoveAfflictions(Func<Affliction, bool> predicate)
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(affliction => predicate(affliction)));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.Remove(affliction);
|
afflictions.TryRemove(affliction, out _);
|
||||||
}
|
}
|
||||||
CalculateVitality();
|
CalculateVitality();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemoveAllAfflictions()
|
public void RemoveAllAfflictions()
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.Contains(a)));
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a => !irremovableAfflictions.ContainsKey(a)));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
//set strength to 0 in case the affliction needs to react to becoming inactive
|
//set strength to 0 in case the affliction needs to react to becoming inactive
|
||||||
affliction.Strength = 0.0f;
|
affliction.Strength = 0.0f;
|
||||||
afflictions.Remove(affliction);
|
afflictions.TryRemove(affliction, out _);
|
||||||
}
|
}
|
||||||
foreach (Affliction affliction in irremovableAfflictions)
|
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
||||||
{
|
{
|
||||||
affliction.Strength = 0.0f;
|
affliction.Strength = 0.0f;
|
||||||
}
|
}
|
||||||
@@ -768,17 +788,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void RemoveNegativeAfflictions()
|
public void RemoveNegativeAfflictions()
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
afflictionsToRemove.AddRange(afflictions.Keys.Where(a =>
|
||||||
!irremovableAfflictions.Contains(a) &&
|
!irremovableAfflictions.ContainsKey(a) &&
|
||||||
!a.Prefab.IsBuff &&
|
!a.Prefab.IsBuff &&
|
||||||
a.Prefab.AfflictionType != "geneticmaterialbuff" &&
|
a.Prefab.AfflictionType != "geneticmaterialbuff" &&
|
||||||
a.Prefab.AfflictionType != "geneticmaterialdebuff"));
|
a.Prefab.AfflictionType != "geneticmaterialdebuff"));
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.Remove(affliction);
|
afflictions.TryRemove(affliction, out _);
|
||||||
}
|
}
|
||||||
foreach (Affliction affliction in irremovableAfflictions)
|
foreach (Affliction affliction in irremovableAfflictions.Keys)
|
||||||
{
|
{
|
||||||
affliction.Strength = 0.0f;
|
affliction.Strength = 0.0f;
|
||||||
}
|
}
|
||||||
@@ -881,7 +902,7 @@ namespace Barotrauma
|
|||||||
var copyAffliction = newAffliction.Prefab.Instantiate(
|
var copyAffliction = newAffliction.Prefab.Instantiate(
|
||||||
Math.Min(newAffliction.Prefab.MaxStrength, modifiedStrength),
|
Math.Min(newAffliction.Prefab.MaxStrength, modifiedStrength),
|
||||||
newAffliction.Source);
|
newAffliction.Source);
|
||||||
afflictions.Add(copyAffliction, limbHealth);
|
afflictions.TryAdd(copyAffliction, limbHealth);
|
||||||
AchievementManager.OnAfflictionReceived(copyAffliction, Character);
|
AchievementManager.OnAfflictionReceived(copyAffliction, Character);
|
||||||
MedicalClinic.OnAfflictionCountChanged(Character);
|
MedicalClinic.OnAfflictionCountChanged(Character);
|
||||||
|
|
||||||
@@ -918,6 +939,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!Character.GodMode)
|
if (!Character.GodMode)
|
||||||
{
|
{
|
||||||
|
var afflictionsToRemove = AfflictionsToRemove;
|
||||||
|
var afflictionsToUpdate = AfflictionsToUpdate;
|
||||||
afflictionsToRemove.Clear();
|
afflictionsToRemove.Clear();
|
||||||
afflictionsToUpdate.Clear();
|
afflictionsToUpdate.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
@@ -926,7 +949,7 @@ namespace Barotrauma
|
|||||||
if (affliction.Strength <= 0.0f)
|
if (affliction.Strength <= 0.0f)
|
||||||
{
|
{
|
||||||
AchievementManager.OnAfflictionRemoved(affliction, Character);
|
AchievementManager.OnAfflictionRemoved(affliction, Character);
|
||||||
if (!irremovableAfflictions.Contains(affliction)) { afflictionsToRemove.Add(affliction); }
|
if (!irremovableAfflictions.ContainsKey(affliction)) { afflictionsToRemove.Add(affliction); }
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (affliction.Prefab.Duration > 0.0f)
|
if (affliction.Prefab.Duration > 0.0f)
|
||||||
@@ -964,7 +987,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (var affliction in afflictionsToRemove)
|
foreach (var affliction in afflictionsToRemove)
|
||||||
{
|
{
|
||||||
afflictions.Remove(affliction);
|
afflictions.TryRemove(affliction, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (afflictionsToRemove.Count is not 0)
|
if (afflictionsToRemove.Count is not 0)
|
||||||
@@ -1212,9 +1235,14 @@ namespace Barotrauma
|
|||||||
return (causeOfDeath, strongestAffliction);
|
return (causeOfDeath, strongestAffliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Affliction> allAfflictions = new List<Affliction>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_allAfflictions;
|
||||||
|
private static List<Affliction> AllAfflictionsList => t_allAfflictions ??= new List<Affliction>();
|
||||||
|
|
||||||
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
private IEnumerable<Affliction> GetAllAfflictions(bool mergeSameAfflictions, Func<Affliction, bool> predicate = null)
|
||||||
{
|
{
|
||||||
|
var allAfflictions = AllAfflictionsList;
|
||||||
allAfflictions.Clear();
|
allAfflictions.Clear();
|
||||||
if (!mergeSameAfflictions)
|
if (!mergeSameAfflictions)
|
||||||
{
|
{
|
||||||
@@ -1397,10 +1425,17 @@ namespace Barotrauma
|
|||||||
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
return MathHelper.Clamp(strength, 0.0f, affliction.Prefab.MaxStrength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Affliction> activeAfflictions = new List<Affliction>();
|
// Thread-static to avoid concurrent modification in parallel updates
|
||||||
private readonly List<(LimbHealth limbHealth, Affliction affliction)> limbAfflictions = new List<(LimbHealth limbHealth, Affliction affliction)>();
|
[ThreadStatic]
|
||||||
|
private static List<Affliction> t_activeAfflictions;
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<(LimbHealth limbHealth, Affliction affliction)> t_limbAfflictions;
|
||||||
|
private static List<Affliction> ActiveAfflictionsList => t_activeAfflictions ??= new List<Affliction>();
|
||||||
|
private static List<(LimbHealth limbHealth, Affliction affliction)> LimbAfflictionsList => t_limbAfflictions ??= new List<(LimbHealth limbHealth, Affliction affliction)>();
|
||||||
|
|
||||||
public void ServerWrite(IWriteMessage msg)
|
public void ServerWrite(IWriteMessage msg)
|
||||||
{
|
{
|
||||||
|
var activeAfflictions = ActiveAfflictionsList;
|
||||||
activeAfflictions.Clear();
|
activeAfflictions.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
@@ -1426,6 +1461,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var limbAfflictions = LimbAfflictionsList;
|
||||||
limbAfflictions.Clear();
|
limbAfflictions.Clear();
|
||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
@@ -1455,8 +1491,9 @@ namespace Barotrauma
|
|||||||
public void Remove()
|
public void Remove()
|
||||||
{
|
{
|
||||||
RemoveProjSpecific();
|
RemoveProjSpecific();
|
||||||
afflictionsToRemove.Clear();
|
// Clear thread-static lists to help with garbage collection
|
||||||
afflictionsToUpdate.Clear();
|
AfflictionsToRemove.Clear();
|
||||||
|
AfflictionsToUpdate.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void RemoveProjSpecific();
|
partial void RemoveProjSpecific();
|
||||||
@@ -1531,14 +1568,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
if (afflictionPredicate != null && !afflictionPredicate.Invoke(afflictionPrefab)) { return; }
|
||||||
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
float strength = afflictionElement.GetAttributeFloat("strength", 0.0f);
|
||||||
var irremovableAffliction = irremovableAfflictions.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
var irremovableAffliction = irremovableAfflictions.Keys.FirstOrDefault(a => a.Prefab == afflictionPrefab);
|
||||||
if (irremovableAffliction != null)
|
if (irremovableAffliction != null)
|
||||||
{
|
{
|
||||||
irremovableAffliction.Strength = strength;
|
irremovableAffliction.Strength = strength;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
afflictions.Add(afflictionPrefab.Instantiate(strength), limbHealth);
|
afflictions.TryAdd(afflictionPrefab.Instantiate(strength), limbHealth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,16 +282,27 @@ namespace Barotrauma
|
|||||||
#if SERVER
|
#if SERVER
|
||||||
if (GameMain.Server != null && Entity.Spawner != null && createNetworkEvents)
|
if (GameMain.Server != null && Entity.Spawner != null && createNetworkEvents)
|
||||||
{
|
{
|
||||||
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
|
try
|
||||||
{
|
{
|
||||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
if (GameMain.Server.EntityEventManager.UniqueEvents.ToList().Any(ev => ev.Entity == item))
|
||||||
DebugConsole.ThrowError(errorMsg);
|
{
|
||||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
DebugConsole.ThrowError(errorMsg);
|
||||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||||
|
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
#if SERVER
|
||||||
|
Networking.GameServer.Log("Try making UniqueEvents snapshot failed", Networking.ServerLog.MessageType.Error);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (itemElement.GetAttributeBool("equip", false))
|
if (itemElement.GetAttributeBool("equip", false))
|
||||||
|
|||||||
@@ -170,16 +170,28 @@ namespace Barotrauma
|
|||||||
#if SERVER
|
#if SERVER
|
||||||
if (GameMain.Server != null && Entity.Spawner != null)
|
if (GameMain.Server != null && Entity.Spawner != null)
|
||||||
{
|
{
|
||||||
if (GameMain.Server.EntityEventManager.UniqueEvents.Any(ev => ev.Entity == item))
|
try
|
||||||
{
|
{
|
||||||
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
if (GameMain.Server.EntityEventManager.UniqueEvents.ToList().Any(ev => ev.Entity == item))
|
||||||
DebugConsole.ThrowError(errorMsg);
|
{
|
||||||
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
string errorMsg = $"Error while spawning job items. Item {item.Name} created network events before the spawn event had been created.";
|
||||||
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
DebugConsole.ThrowError(errorMsg);
|
||||||
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
GameAnalyticsManager.AddErrorEventOnce("Job.InitializeJobItem:EventsBeforeSpawning", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
GameMain.Server.EntityEventManager.UniqueEvents.RemoveAll(ev => ev.Entity == item);
|
||||||
|
GameMain.Server.EntityEventManager.Events.RemoveAll(ev => ev.Entity == item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
#if SERVER
|
||||||
|
Networking.GameServer.Log("Try making UniqueEvents snapshot failed", Networking.ServerLog.MessageType.Error);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
Entity.Spawner.CreateNetworkEvent(new EntitySpawner.SpawnEntity(item));
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -797,16 +797,14 @@ namespace Barotrauma
|
|||||||
return AddDamage(simPosition, afflictions, playSound);
|
return AddDamage(simPosition, afflictions, playSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<DamageModifier> appliedDamageModifiers = new List<DamageModifier>();
|
// Thread-safe: using local variables instead of instance fields to avoid concurrent modification
|
||||||
private readonly List<DamageModifier> tempModifiers = new List<DamageModifier>();
|
|
||||||
private readonly List<Affliction> afflictionsCopy = new List<Affliction>();
|
|
||||||
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
public AttackResult AddDamage(Vector2 simPosition, IEnumerable<Affliction> afflictions, bool playSound, float damageMultiplier = 1, float penetration = 0f, Character attacker = null)
|
||||||
{
|
{
|
||||||
appliedDamageModifiers.Clear();
|
var appliedDamageModifiers = new List<DamageModifier>();
|
||||||
afflictionsCopy.Clear();
|
var afflictionsCopy = new List<Affliction>();
|
||||||
foreach (var affliction in afflictions)
|
foreach (var affliction in afflictions)
|
||||||
{
|
{
|
||||||
tempModifiers.Clear();
|
var tempModifiers = new List<DamageModifier>();
|
||||||
var newAffliction = affliction;
|
var newAffliction = affliction;
|
||||||
float random = Rand.Value(Rand.RandSync.Unsynced);
|
float random = Rand.Value(Rand.RandSync.Unsynced);
|
||||||
bool foundMatchingModifier = false;
|
bool foundMatchingModifier = false;
|
||||||
@@ -1022,13 +1020,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime);
|
partial void UpdateProjSpecific(float deltaTime);
|
||||||
|
|
||||||
private readonly List<Body> contactBodies = new List<Body>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<Body> t_contactBodies;
|
||||||
|
private static List<Body> ContactBodies => t_contactBodies ??= new List<Body>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1, Limb targetLimb = null)
|
||||||
{
|
{
|
||||||
attackResult = default;
|
attackResult = default;
|
||||||
|
var contactBodies = ContactBodies;
|
||||||
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
Vector2 simPos = ragdoll.SimplePhysicsEnabled ? character.SimPosition : SimPosition;
|
||||||
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(simPos, attackSimPos));
|
||||||
bool wasRunning = attack.IsRunning;
|
bool wasRunning = attack.IsRunning;
|
||||||
@@ -1287,7 +1290,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
|
// Thread-static to avoid concurrent modification in parallel item updates
|
||||||
|
[ThreadStatic]
|
||||||
|
private static List<ISerializableEntity> t_statusEffectTargets;
|
||||||
|
private static List<ISerializableEntity> StatusEffectTargets => t_statusEffectTargets ??= new List<ISerializableEntity>();
|
||||||
|
|
||||||
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
public void ApplyStatusEffects(ActionType actionType, float deltaTime)
|
||||||
{
|
{
|
||||||
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
if (!statusEffects.TryGetValue(actionType, out var statusEffectList)) { return; }
|
||||||
@@ -1310,6 +1317,7 @@ namespace Barotrauma
|
|||||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||||
{
|
{
|
||||||
|
var targets = StatusEffectTargets;
|
||||||
targets.Clear();
|
targets.Clear();
|
||||||
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
statusEffect.AddNearbyTargets(WorldPosition, targets);
|
||||||
statusEffect.Apply(actionType, deltaTime, character, targets);
|
statusEffect.Apply(actionType, deltaTime, character, targets);
|
||||||
|
|||||||
+14
-20
@@ -1,10 +1,12 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
@@ -117,8 +119,9 @@ namespace Barotrauma
|
|||||||
public virtual AnimationType AnimationType { get; protected set; }
|
public virtual AnimationType AnimationType { get; protected set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The cached animations of all the characters that have been loaded.
|
/// The cached animations of all the characters that have been loaded.
|
||||||
|
/// Thread-safe cache using ConcurrentDictionary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<Identifier, Dictionary<string, AnimationParams>> allAnimations = new Dictionary<Identifier, Dictionary<string, AnimationParams>>();
|
private static readonly ConcurrentDictionary<Identifier, ConcurrentDictionary<string, AnimationParams>> allAnimations = new ConcurrentDictionary<Identifier, ConcurrentDictionary<string, AnimationParams>>();
|
||||||
|
|
||||||
[Header("Movement")]
|
[Header("Movement")]
|
||||||
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
|
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(DecimalCount = 2, MinValueFloat = 0, MaxValueFloat = Ragdoll.MAX_SPEED, ValueStep = 0.1f)]
|
||||||
@@ -244,7 +247,9 @@ namespace Barotrauma
|
|||||||
return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
|
return GetAnimParams<T>(speciesName, animSpecies, fallbackSpecies: character.Prefab.GetBaseCharacterSpeciesName(speciesName), animType, file, throwErrors);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly List<string> errorMessages = new List<string>();
|
// ThreadLocal for thread-safe error message collection during animation loading
|
||||||
|
private static readonly ThreadLocal<List<string>> errorMessagesLocal = new ThreadLocal<List<string>>(() => new List<string>());
|
||||||
|
private static List<string> errorMessages => errorMessagesLocal.Value;
|
||||||
|
|
||||||
private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
private static T GetAnimParams<T>(Identifier speciesName, Identifier animSpecies, Identifier fallbackSpecies, AnimationType animType, Either<string, ContentPath> file, bool throwErrors = true) where T : AnimationParams, new()
|
||||||
{
|
{
|
||||||
@@ -262,11 +267,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
|
ContentPackage contentPackage = contentPath?.ContentPackage ?? CharacterPrefab.FindBySpeciesName(speciesName)?.ContentPackage;
|
||||||
Debug.Assert(contentPackage != null);
|
Debug.Assert(contentPackage != null);
|
||||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> animations))
|
var animations = allAnimations.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, AnimationParams>());
|
||||||
{
|
|
||||||
animations = new Dictionary<string, AnimationParams>();
|
|
||||||
allAnimations.Add(speciesName, animations);
|
|
||||||
}
|
|
||||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
|
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(animSpecies, animType);
|
||||||
if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
|
if (animations.TryGetValue(key, out AnimationParams anim) && anim.AnimationType == animType)
|
||||||
{
|
{
|
||||||
@@ -418,16 +419,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
throw new Exception("Cannot create an animation file of type " + animationType);
|
throw new Exception("Cannot create an animation file of type " + animationType);
|
||||||
}
|
}
|
||||||
if (!allAnimations.TryGetValue(speciesName, out Dictionary<string, AnimationParams> anims))
|
var anims = allAnimations.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, AnimationParams>());
|
||||||
{
|
|
||||||
anims = new Dictionary<string, AnimationParams>();
|
|
||||||
allAnimations.Add(speciesName, anims);
|
|
||||||
}
|
|
||||||
string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
string fileName = IO.Path.GetFileNameWithoutExtension(fullPath);
|
||||||
if (anims.ContainsKey(fileName))
|
if (anims.ContainsKey(fileName))
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
DebugConsole.NewMessage($"[AnimationParams] Removing the old animation of type {animationType}.", Color.Red);
|
||||||
anims.Remove(fileName);
|
anims.TryRemove(fileName, out _);
|
||||||
}
|
}
|
||||||
var instance = new T();
|
var instance = new T();
|
||||||
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
XElement animationElement = new XElement(GetDefaultFileName(speciesName, animationType), new XAttribute("animationtype", animationType.ToString()));
|
||||||
@@ -439,7 +436,7 @@ namespace Barotrauma
|
|||||||
instance.IsLoaded = instance.Deserialize(animationElement);
|
instance.IsLoaded = instance.Deserialize(animationElement);
|
||||||
instance.Save();
|
instance.Save();
|
||||||
instance.Load(contentPath, speciesName);
|
instance.Load(contentPath, speciesName);
|
||||||
anims.Add(fileName, instance);
|
anims.TryAdd(fileName, instance);
|
||||||
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
DebugConsole.NewMessage($"[AnimationParams] New animation file of type {animationType} created.", Color.GhostWhite);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -467,17 +464,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// Update the key by removing and re-adding the animation.
|
// Update the key by removing and re-adding the animation.
|
||||||
string fileName = FileNameWithoutExtension;
|
string fileName = FileNameWithoutExtension;
|
||||||
if (allAnimations.TryGetValue(SpeciesName, out Dictionary<string, AnimationParams> animations))
|
if (allAnimations.TryGetValue(SpeciesName, out ConcurrentDictionary<string, AnimationParams> animations))
|
||||||
{
|
{
|
||||||
animations.Remove(fileName);
|
animations.TryRemove(fileName, out _);
|
||||||
}
|
}
|
||||||
base.UpdatePath(newPath);
|
base.UpdatePath(newPath);
|
||||||
if (animations != null)
|
if (animations != null)
|
||||||
{
|
{
|
||||||
if (!animations.ContainsKey(fileName))
|
animations.TryAdd(fileName, this);
|
||||||
{
|
|
||||||
animations.Add(fileName, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-16
@@ -1,5 +1,6 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
@@ -124,8 +125,9 @@ namespace Barotrauma
|
|||||||
/// key1: Species name
|
/// key1: Species name
|
||||||
/// key2: File path
|
/// key2: File path
|
||||||
/// value: Ragdoll parameters
|
/// value: Ragdoll parameters
|
||||||
|
/// Thread-safe cache using ConcurrentDictionary.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static readonly Dictionary<Identifier, Dictionary<string, RagdollParams>> allRagdolls = new Dictionary<Identifier, Dictionary<string, RagdollParams>>();
|
private static readonly ConcurrentDictionary<Identifier, ConcurrentDictionary<string, RagdollParams>> allRagdolls = new ConcurrentDictionary<Identifier, ConcurrentDictionary<string, RagdollParams>>();
|
||||||
|
|
||||||
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
|
public List<ColliderParams> Colliders { get; private set; } = new List<ColliderParams>();
|
||||||
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
public List<LimbParams> Limbs { get; private set; } = new List<LimbParams>();
|
||||||
@@ -222,11 +224,7 @@ namespace Barotrauma
|
|||||||
Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
|
Debug.Assert(!fileName.IsNullOrWhiteSpace() || !contentPath.IsNullOrWhiteSpace());
|
||||||
}
|
}
|
||||||
Debug.Assert(contentPackage != null);
|
Debug.Assert(contentPackage != null);
|
||||||
if (!allRagdolls.TryGetValue(speciesName, out Dictionary<string, RagdollParams> ragdolls))
|
var ragdolls = allRagdolls.GetOrAdd(speciesName, _ => new ConcurrentDictionary<string, RagdollParams>());
|
||||||
{
|
|
||||||
ragdolls = new Dictionary<string, RagdollParams>();
|
|
||||||
allRagdolls.Add(speciesName, ragdolls);
|
|
||||||
}
|
|
||||||
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
|
string key = fileName ?? contentPath?.Value ?? GetDefaultFileName(ragdollSpecies);
|
||||||
if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
|
if (ragdolls.TryGetValue(key, out RagdollParams ragdoll))
|
||||||
{
|
{
|
||||||
@@ -331,10 +329,10 @@ namespace Barotrauma
|
|||||||
if (allRagdolls.ContainsKey(speciesName))
|
if (allRagdolls.ContainsKey(speciesName))
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
DebugConsole.NewMessage($"[RagdollParams] Removing the old ragdolls from {speciesName}.", Color.Red);
|
||||||
allRagdolls.Remove(speciesName);
|
allRagdolls.TryRemove(speciesName, out _);
|
||||||
}
|
}
|
||||||
var ragdolls = new Dictionary<string, RagdollParams>();
|
var ragdolls = new ConcurrentDictionary<string, RagdollParams>();
|
||||||
allRagdolls.Add(speciesName, ragdolls);
|
allRagdolls.TryAdd(speciesName, ragdolls);
|
||||||
var instance = new T
|
var instance = new T
|
||||||
{
|
{
|
||||||
doc = new XDocument(mainElement)
|
doc = new XDocument(mainElement)
|
||||||
@@ -345,7 +343,7 @@ namespace Barotrauma
|
|||||||
instance.IsLoaded = instance.Deserialize(mainElement);
|
instance.IsLoaded = instance.Deserialize(mainElement);
|
||||||
instance.Save();
|
instance.Save();
|
||||||
instance.Load(contentPath, speciesName);
|
instance.Load(contentPath, speciesName);
|
||||||
ragdolls.Add(instance.FileNameWithoutExtension, instance);
|
ragdolls.TryAdd(instance.FileNameWithoutExtension, instance);
|
||||||
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
DebugConsole.NewMessage("[RagdollParams] New default ragdoll params successfully created at " + fullPath, Color.NavajoWhite);
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
@@ -362,17 +360,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// Update the key by removing and re-adding the ragdoll.
|
// Update the key by removing and re-adding the ragdoll.
|
||||||
string fileName = FileNameWithoutExtension;
|
string fileName = FileNameWithoutExtension;
|
||||||
if (allRagdolls.TryGetValue(SpeciesName, out Dictionary<string, RagdollParams> ragdolls))
|
if (allRagdolls.TryGetValue(SpeciesName, out ConcurrentDictionary<string, RagdollParams> ragdolls))
|
||||||
{
|
{
|
||||||
ragdolls.Remove(fileName);
|
ragdolls.TryRemove(fileName, out _);
|
||||||
}
|
}
|
||||||
base.UpdatePath(fullPath);
|
base.UpdatePath(fullPath);
|
||||||
if (ragdolls != null)
|
if (ragdolls != null)
|
||||||
{
|
{
|
||||||
if (!ragdolls.ContainsKey(fileName))
|
ragdolls.TryAdd(fileName, this);
|
||||||
{
|
|
||||||
ragdolls.Add(fileName, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using Barotrauma.Abilities;
|
using Barotrauma.Abilities;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -72,7 +74,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly HashSet<Identifier> checkedNonStackableTalents = new();
|
// ThreadLocal for thread-safe talent checking
|
||||||
|
private static readonly ThreadLocal<HashSet<Identifier>> checkedNonStackableTalentsLocal = new ThreadLocal<HashSet<Identifier>>(() => new HashSet<Identifier>());
|
||||||
|
private static HashSet<Identifier> checkedNonStackableTalents => checkedNonStackableTalentsLocal.Value;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Checks talents for a given AbilityObject taking into account non-stackable talents.
|
/// Checks talents for a given AbilityObject taking into account non-stackable talents.
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ namespace Barotrauma
|
|||||||
var npcConversationCollection = new NPCConversationCollection(this, mainElement);
|
var npcConversationCollection = new NPCConversationCollection(this, mainElement);
|
||||||
if (!NPCConversationCollection.Collections.ContainsKey(npcConversationCollection.Language))
|
if (!NPCConversationCollection.Collections.ContainsKey(npcConversationCollection.Language))
|
||||||
{
|
{
|
||||||
NPCConversationCollection.Collections.Add(npcConversationCollection.Language, new PrefabCollection<NPCConversationCollection>());
|
NPCConversationCollection.Collections.TryAdd(npcConversationCollection.Language, new PrefabCollection<NPCConversationCollection>());
|
||||||
}
|
}
|
||||||
NPCConversationCollection.Collections[npcConversationCollection.Language].Add(npcConversationCollection, allowOverriding);
|
NPCConversationCollection.Collections[npcConversationCollection.Language].Add(npcConversationCollection, allowOverriding);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -590,6 +590,11 @@ namespace Barotrauma
|
|||||||
package.UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId workshopId && workshopId.Value == childUgcItemId.Value));
|
package.UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId workshopId && workshopId.Value == childUgcItemId.Value));
|
||||||
foreach (var missingChild in missingChildren)
|
foreach (var missingChild in missingChildren)
|
||||||
{
|
{
|
||||||
|
if (missingChild.ToString() == "2559634234" ||
|
||||||
|
missingChild.ToString() == "2795927223")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
enabledPackage.AddMissingDependency(missingChild);
|
enabledPackage.AddMissingDependency(missingChild);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1478,7 +1478,7 @@ namespace Barotrauma
|
|||||||
newItemName = args[2];
|
newItemName = args[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
var oldItem = Item.ItemList.FindAll(it => it.Name == args[0]).ElementAtOrDefault(itemIndex);
|
var oldItem = Item.ItemList.Where(it => it.Name == args[0]).ElementAtOrDefault(itemIndex);
|
||||||
if (oldItem == null)
|
if (oldItem == null)
|
||||||
{
|
{
|
||||||
ThrowError($"Could not find an item with the name {args[0]} (index {itemIndex}).");
|
ThrowError($"Could not find an item with the name {args[0]} (index {itemIndex}).");
|
||||||
@@ -1852,7 +1852,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) =>
|
commands.Add(new Command("power", "power: Immediately powers up the submarine's nuclear reactor.", (string[] args) =>
|
||||||
{
|
{
|
||||||
Item reactorItem = Item.ItemList.Find(i => i.GetComponent<Reactor>() != null);
|
Item reactorItem = Item.ItemList.FirstOrDefault(i => i.GetComponent<Reactor>() != null);
|
||||||
if (reactorItem == null) { return; }
|
if (reactorItem == null) { return; }
|
||||||
|
|
||||||
var reactor = reactorItem.GetComponent<Reactor>();
|
var reactor = reactorItem.GetComponent<Reactor>();
|
||||||
@@ -3230,7 +3230,7 @@ namespace Barotrauma
|
|||||||
if (args.Length > spawnLocationIndex + 1)
|
if (args.Length > spawnLocationIndex + 1)
|
||||||
{
|
{
|
||||||
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
|
if (!int.TryParse(args[spawnLocationIndex + 1], NumberStyles.Any, CultureInfo.InvariantCulture, out amount)) { amount = 1; }
|
||||||
amount = Math.Min(amount, 100);
|
amount = Math.Min(amount, 100000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Length > spawnLocationIndex + 2)
|
if (args.Length > spawnLocationIndex + 2)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Immutable;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -10,11 +11,22 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class UnlockPathAction : EventAction
|
class UnlockPathAction : EventAction
|
||||||
{
|
{
|
||||||
private static readonly HashSet<LocationConnection> pathsUnlockedThisRound = new HashSet<LocationConnection>();
|
private static volatile ImmutableHashSet<LocationConnection> _pathsUnlockedThisRound =
|
||||||
|
ImmutableHashSet<LocationConnection>.Empty;
|
||||||
|
|
||||||
public static void ResetPathsUnlockedThisRound()
|
public static void ResetPathsUnlockedThisRound()
|
||||||
{
|
{
|
||||||
pathsUnlockedThisRound.Clear();
|
_pathsUnlockedThisRound = ImmutableHashSet<LocationConnection>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddUnlockedPath(LocationConnection connection)
|
||||||
|
{
|
||||||
|
ImmutableHashSet<LocationConnection> original, updated;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
original = _pathsUnlockedThisRound;
|
||||||
|
updated = original.Add(connection);
|
||||||
|
} while (Interlocked.CompareExchange(ref _pathsUnlockedThisRound, updated, original) != original);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UnlockPathAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
public UnlockPathAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||||
@@ -40,7 +52,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!connection.Locked) { continue; }
|
if (!connection.Locked) { continue; }
|
||||||
connection.Locked = false;
|
connection.Locked = false;
|
||||||
pathsUnlockedThisRound.Add(connection);
|
AddUnlockedPath(connection);
|
||||||
#if SERVER
|
#if SERVER
|
||||||
NotifyUnlock(connection);
|
NotifyUnlock(connection);
|
||||||
#else
|
#else
|
||||||
@@ -61,7 +73,7 @@ namespace Barotrauma
|
|||||||
#if SERVER
|
#if SERVER
|
||||||
public static void NotifyPathsUnlockedThisRound(Client client)
|
public static void NotifyPathsUnlockedThisRound(Client client)
|
||||||
{
|
{
|
||||||
foreach (LocationConnection connection in pathsUnlockedThisRound)
|
foreach (LocationConnection connection in _pathsUnlockedThisRound)
|
||||||
{
|
{
|
||||||
NotifyUnlock(connection, client);
|
NotifyUnlock(connection, client);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ using Barotrauma.Items.Components;
|
|||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -43,7 +46,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private Level level;
|
private Level level;
|
||||||
|
|
||||||
private readonly List<Sprite> preloadedSprites = new List<Sprite>();
|
private volatile ImmutableList<Sprite> _preloadedSprites = ImmutableList<Sprite>.Empty;
|
||||||
|
|
||||||
//The "intensity" of the current situation (a value between 0.0 - 1.0).
|
//The "intensity" of the current situation (a value between 0.0 - 1.0).
|
||||||
//High when a disaster has struck, low when nothing special is going on.
|
//High when a disaster has struck, low when nothing special is going on.
|
||||||
@@ -83,14 +86,18 @@ namespace Barotrauma
|
|||||||
private float crewAwayResetTimer;
|
private float crewAwayResetTimer;
|
||||||
private float crewAwayDuration;
|
private float crewAwayDuration;
|
||||||
|
|
||||||
private readonly List<EventSet> pendingEventSets = new List<EventSet>();
|
// volatile + ImmutableCollections
|
||||||
|
private volatile ImmutableList<EventSet> _pendingEventSets = ImmutableList<EventSet>.Empty;
|
||||||
|
|
||||||
private readonly Dictionary<EventSet, List<Event>> selectedEvents = new Dictionary<EventSet, List<Event>>();
|
private volatile ImmutableDictionary<EventSet, ImmutableList<Event>> _selectedEvents =
|
||||||
|
ImmutableDictionary<EventSet, ImmutableList<Event>>.Empty;
|
||||||
|
|
||||||
private readonly List<Event> activeEvents = new List<Event>();
|
private volatile ImmutableList<Event> _activeEvents = ImmutableList<Event>.Empty;
|
||||||
|
|
||||||
private readonly HashSet<Event> finishedEvents = new HashSet<Event>();
|
private volatile ImmutableHashSet<Event> _finishedEvents = ImmutableHashSet<Event>.Empty;
|
||||||
private readonly HashSet<Identifier> nonRepeatableEvents = new HashSet<Identifier>();
|
private volatile ImmutableHashSet<Identifier> _nonRepeatableEvents = ImmutableHashSet<Identifier>.Empty;
|
||||||
|
|
||||||
|
private volatile ImmutableQueue<Action> _deferredActions = ImmutableQueue<Action>.Empty;
|
||||||
|
|
||||||
|
|
||||||
#if DEBUG && SERVER
|
#if DEBUG && SERVER
|
||||||
@@ -112,10 +119,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public IEnumerable<Event> ActiveEvents
|
public IEnumerable<Event> ActiveEvents
|
||||||
{
|
{
|
||||||
get { return activeEvents; }
|
get { return _activeEvents; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly Queue<Event> QueuedEvents = new Queue<Event>();
|
public readonly ConcurrentQueue<Event> QueuedEvents = new ConcurrentQueue<Event>();
|
||||||
|
|
||||||
public readonly Queue<Identifier> QueuedEventsForNextRound = new Queue<Identifier>();
|
public readonly Queue<Identifier> QueuedEventsForNextRound = new Queue<Identifier>();
|
||||||
|
|
||||||
@@ -131,8 +138,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<TimeStamp> timeStamps = new List<TimeStamp>();
|
private volatile ImmutableList<TimeStamp> _timeStamps = ImmutableList<TimeStamp>.Empty;
|
||||||
public void AddTimeStamp(Event e) => timeStamps.Add(new TimeStamp(e));
|
public void AddTimeStamp(Event e) => AtomicUpdate(ref _timeStamps, list => list.Add(new TimeStamp(e)));
|
||||||
|
|
||||||
public readonly EventLog EventLog = new EventLog();
|
public readonly EventLog EventLog = new EventLog();
|
||||||
|
|
||||||
@@ -143,6 +150,72 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public bool Enabled = true;
|
public bool Enabled = true;
|
||||||
|
|
||||||
|
private static T AtomicUpdate<T>(ref T location, Func<T, T> updateFunc) where T : class
|
||||||
|
{
|
||||||
|
T original, updated;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
original = Volatile.Read(ref location);
|
||||||
|
updated = updateFunc(original);
|
||||||
|
} while (Interlocked.CompareExchange(ref location, updated, original) != original);
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// activeEvents
|
||||||
|
private void AddActiveEvent(Event ev) => AtomicUpdate(ref _activeEvents, list => list.Add(ev));
|
||||||
|
private void ClearActiveEvents() => _activeEvents = ImmutableList<Event>.Empty;
|
||||||
|
|
||||||
|
// pendingEventSets
|
||||||
|
private void AddPendingEventSet(EventSet eventSet) =>
|
||||||
|
AtomicUpdate(ref _pendingEventSets, list => list.Contains(eventSet) ? list : list.Add(eventSet));
|
||||||
|
private void RemovePendingEventSetAt(int index) =>
|
||||||
|
AtomicUpdate(ref _pendingEventSets, list => index < list.Count ? list.RemoveAt(index) : list);
|
||||||
|
private void ClearPendingEventSets() => _pendingEventSets = ImmutableList<EventSet>.Empty;
|
||||||
|
|
||||||
|
// selectedEvents
|
||||||
|
private void AddSelectedEvent(EventSet eventSet, Event ev) =>
|
||||||
|
AtomicUpdate(ref _selectedEvents, dict =>
|
||||||
|
{
|
||||||
|
var currentList = dict.GetValueOrDefault(eventSet, ImmutableList<Event>.Empty);
|
||||||
|
return dict.SetItem(eventSet, currentList.Add(ev));
|
||||||
|
});
|
||||||
|
private void RemoveSelectedEventSet(EventSet eventSet) =>
|
||||||
|
AtomicUpdate(ref _selectedEvents, dict => dict.Remove(eventSet));
|
||||||
|
private void ClearSelectedEvents() =>
|
||||||
|
_selectedEvents = ImmutableDictionary<EventSet, ImmutableList<Event>>.Empty;
|
||||||
|
private ImmutableList<Event> GetSelectedEvents(EventSet eventSet) =>
|
||||||
|
_selectedEvents.GetValueOrDefault(eventSet, ImmutableList<Event>.Empty);
|
||||||
|
private bool HasSelectedEvents(EventSet eventSet) => _selectedEvents.ContainsKey(eventSet);
|
||||||
|
|
||||||
|
// finishedEvents
|
||||||
|
private void AddFinishedEvent(Event ev) => AtomicUpdate(ref _finishedEvents, set => set.Add(ev));
|
||||||
|
private void ClearFinishedEvents() => _finishedEvents = ImmutableHashSet<Event>.Empty;
|
||||||
|
private bool IsEventFinished(Event ev) => _finishedEvents.Contains(ev);
|
||||||
|
|
||||||
|
// nonRepeatableEvents
|
||||||
|
private void AddNonRepeatableEvent(Identifier id) => AtomicUpdate(ref _nonRepeatableEvents, set => set.Add(id));
|
||||||
|
private void ClearNonRepeatableEvents() => _nonRepeatableEvents = ImmutableHashSet<Identifier>.Empty;
|
||||||
|
|
||||||
|
// preloadedSprites
|
||||||
|
private void AddPreloadedSprite(Sprite sprite) => AtomicUpdate(ref _preloadedSprites, list => list.Add(sprite));
|
||||||
|
private void ClearPreloadedSprites()
|
||||||
|
{
|
||||||
|
var sprites = Interlocked.Exchange(ref _preloadedSprites, ImmutableList<Sprite>.Empty);
|
||||||
|
foreach (var s in sprites) { s.Remove(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// timeStamps
|
||||||
|
private void ClearTimeStamps() => _timeStamps = ImmutableList<TimeStamp>.Empty;
|
||||||
|
|
||||||
|
private void EnqueueDeferredAction(Action action) =>
|
||||||
|
AtomicUpdate(ref _deferredActions, queue => queue.Enqueue(action));
|
||||||
|
private void ProcessDeferredActions()
|
||||||
|
{
|
||||||
|
var actions = Interlocked.Exchange(ref _deferredActions, ImmutableQueue<Action>.Empty);
|
||||||
|
foreach (var action in actions) { action(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private MTRandom random;
|
private MTRandom random;
|
||||||
public int RandomSeed { get; private set; }
|
public int RandomSeed { get; private set; }
|
||||||
|
|
||||||
@@ -152,15 +225,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (isClient) { return; }
|
if (isClient) { return; }
|
||||||
|
|
||||||
timeStamps.Clear();
|
ClearTimeStamps();
|
||||||
pendingEventSets.Clear();
|
ClearPendingEventSets();
|
||||||
selectedEvents.Clear();
|
ClearSelectedEvents();
|
||||||
activeEvents.Clear();
|
ClearActiveEvents();
|
||||||
#if SERVER
|
#if SERVER
|
||||||
MissionAction.ResetMissionsUnlockedThisRound();
|
MissionAction.ResetMissionsUnlockedThisRound();
|
||||||
UnlockPathAction.ResetPathsUnlockedThisRound();
|
UnlockPathAction.ResetPathsUnlockedThisRound();
|
||||||
#endif
|
#endif
|
||||||
pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||||
totalPathLength = 0.0f;
|
totalPathLength = 0.0f;
|
||||||
if (level != null)
|
if (level != null)
|
||||||
{
|
{
|
||||||
@@ -235,8 +308,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
void AddSet(EventSet eventSet)
|
void AddSet(EventSet eventSet)
|
||||||
{
|
{
|
||||||
if (pendingEventSets.Contains(eventSet)) { return; }
|
if (_pendingEventSets.Contains(eventSet)) { return; }
|
||||||
pendingEventSets.Add(eventSet);
|
AddPendingEventSet(eventSet);
|
||||||
CreateEvents(eventSet);
|
CreateEvents(eventSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +360,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
||||||
{
|
{
|
||||||
nonRepeatableEvents.Add(ep.Identifier);
|
AddNonRepeatableEvent(ep.Identifier);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (EventSet childSet in eventSet.ChildSets)
|
foreach (EventSet childSet in eventSet.ChildSets)
|
||||||
@@ -332,13 +405,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void ActivateEvent(Event newEvent)
|
public void ActivateEvent(Event newEvent)
|
||||||
{
|
{
|
||||||
activeEvents.Add(newEvent);
|
AddActiveEvent(newEvent);
|
||||||
newEvent.Init();
|
newEvent.Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearEvents()
|
public void ClearEvents()
|
||||||
{
|
{
|
||||||
activeEvents.Clear();
|
ClearActiveEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectSettings()
|
private void SelectSettings()
|
||||||
@@ -391,7 +464,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public IEnumerable<ContentFile> GetFilesToPreload()
|
public IEnumerable<ContentFile> GetFilesToPreload()
|
||||||
{
|
{
|
||||||
foreach (List<Event> eventList in selectedEvents.Values)
|
var snapshot = _selectedEvents;
|
||||||
|
foreach (ImmutableList<Event> eventList in snapshot.Values)
|
||||||
{
|
{
|
||||||
foreach (Event ev in eventList)
|
foreach (Event ev in eventList)
|
||||||
{
|
{
|
||||||
@@ -444,13 +518,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (ContentFile file in filesToPreload)
|
foreach (ContentFile file in filesToPreload)
|
||||||
{
|
{
|
||||||
file.Preload(preloadedSprites.Add);
|
file.Preload(AddPreloadedSprite);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerOnEndRoundActions()
|
public void TriggerOnEndRoundActions()
|
||||||
{
|
{
|
||||||
foreach (var ev in activeEvents)
|
foreach (var ev in _activeEvents)
|
||||||
{
|
{
|
||||||
(ev as ScriptedEvent)?.OnRoundEndAction?.Update(1.0f);
|
(ev as ScriptedEvent)?.OnRoundEndAction?.Update(1.0f);
|
||||||
}
|
}
|
||||||
@@ -458,17 +532,16 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void EndRound()
|
public void EndRound()
|
||||||
{
|
{
|
||||||
pendingEventSets.Clear();
|
ClearPendingEventSets();
|
||||||
selectedEvents.Clear();
|
ClearSelectedEvents();
|
||||||
activeEvents.Clear();
|
ClearActiveEvents();
|
||||||
QueuedEvents.Clear();
|
while (QueuedEvents.TryDequeue(out _)) { } // 清空 ConcurrentQueue
|
||||||
finishedEvents.Clear();
|
ClearFinishedEvents();
|
||||||
nonRepeatableEvents.Clear();
|
ClearNonRepeatableEvents();
|
||||||
|
|
||||||
preloadedSprites.ForEach(s => s.Remove());
|
ClearPreloadedSprites();
|
||||||
preloadedSprites.Clear();
|
|
||||||
|
|
||||||
timeStamps.Clear();
|
ClearTimeStamps();
|
||||||
|
|
||||||
pathFinder = null;
|
pathFinder = null;
|
||||||
}
|
}
|
||||||
@@ -484,7 +557,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (registerFinishedOnly)
|
if (registerFinishedOnly)
|
||||||
{
|
{
|
||||||
foreach (var finishedEvent in finishedEvents)
|
foreach (var finishedEvent in _finishedEvents)
|
||||||
{
|
{
|
||||||
EventSet parentSet = finishedEvent.ParentSet;
|
EventSet parentSet = finishedEvent.ParentSet;
|
||||||
if (parentSet == null) { continue; }
|
if (parentSet == null) { continue; }
|
||||||
@@ -499,7 +572,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values
|
level.LevelData.EventHistory.AddRange(_selectedEvents.Values
|
||||||
.SelectMany(v => v)
|
.SelectMany(v => v)
|
||||||
.Select(e => e.Prefab.Identifier)
|
.Select(e => e.Prefab.Identifier)
|
||||||
.Where(eventId => Register(eventId) && !level.LevelData.EventHistory.Contains(eventId)));
|
.Where(eventId => Register(eventId) && !level.LevelData.EventHistory.Contains(eventId)));
|
||||||
@@ -509,14 +582,14 @@ namespace Barotrauma
|
|||||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(eventId => Register(eventId) && !level.LevelData.NonRepeatableEvents.Contains(eventId)));
|
level.LevelData.NonRepeatableEvents.AddRange(_nonRepeatableEvents.Where(eventId => Register(eventId) && !level.LevelData.NonRepeatableEvents.Contains(eventId)));
|
||||||
|
|
||||||
if (!registerFinishedOnly)
|
if (!registerFinishedOnly)
|
||||||
{
|
{
|
||||||
level.LevelData.FinishedEvents.Clear();
|
level.LevelData.FinishedEvents.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Register(Identifier eventId) => !registerFinishedOnly || finishedEvents.Any(fe => fe.Prefab.Identifier == eventId);
|
bool Register(Identifier eventId) => !registerFinishedOnly || _finishedEvents.Any(fe => fe.Prefab.Identifier == eventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SkipEventCooldown()
|
public void SkipEventCooldown()
|
||||||
@@ -534,7 +607,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void CreateEvents(EventSet eventSet)
|
private void CreateEvents(EventSet eventSet)
|
||||||
{
|
{
|
||||||
selectedEvents.Remove(eventSet);
|
RemoveSelectedEventSet(eventSet);
|
||||||
if (level == null) { return; }
|
if (level == null) { return; }
|
||||||
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
|
if (level.LevelData.HasHuntingGrounds && eventSet.DisableInHuntingGrounds) { return; }
|
||||||
if (eventSet.Exhaustible && level.LevelData.IsEventSetExhausted(eventSet)) { return; }
|
if (eventSet.Exhaustible && level.LevelData.IsEventSetExhausted(eventSet)) { return; }
|
||||||
@@ -601,11 +674,7 @@ namespace Barotrauma
|
|||||||
if (newEvent == null) { continue; }
|
if (newEvent == null) { continue; }
|
||||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||||
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
DebugConsole.NewMessage($"Initialized event {newEvent}", debugOnly: true);
|
||||||
if (!selectedEvents.ContainsKey(eventSet))
|
AddSelectedEvent(eventSet, newEvent);
|
||||||
{
|
|
||||||
selectedEvents.Add(eventSet, new List<Event>());
|
|
||||||
}
|
|
||||||
selectedEvents[eventSet].Add(newEvent);
|
|
||||||
unusedEvents.Remove(subEventPrefab);
|
unusedEvents.Remove(subEventPrefab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -644,11 +713,7 @@ namespace Barotrauma
|
|||||||
var newEvent = eventPrefab.CreateInstance(RandomSeed);
|
var newEvent = eventPrefab.CreateInstance(RandomSeed);
|
||||||
if (newEvent == null) { continue; }
|
if (newEvent == null) { continue; }
|
||||||
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
if (i < spawnPosFilter.Count) { newEvent.SpawnPosFilter = spawnPosFilter[i]; }
|
||||||
if (!selectedEvents.ContainsKey(eventSet))
|
AddSelectedEvent(eventSet, newEvent);
|
||||||
{
|
|
||||||
selectedEvents.Add(eventSet, new List<Event>());
|
|
||||||
}
|
|
||||||
selectedEvents[eventSet].Add(newEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var location = GetEventLocation();
|
var location = GetEventLocation();
|
||||||
@@ -840,9 +905,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!eventsInitialized)
|
if (!eventsInitialized)
|
||||||
{
|
{
|
||||||
foreach (var eventSet in selectedEvents.Keys)
|
var selectedSnapshot = _selectedEvents;
|
||||||
|
foreach (var eventSet in selectedSnapshot.Keys)
|
||||||
{
|
{
|
||||||
foreach (var ev in selectedEvents[eventSet])
|
foreach (var ev in selectedSnapshot[eventSet])
|
||||||
{
|
{
|
||||||
ev.Init(eventSet);
|
ev.Init(eventSet);
|
||||||
}
|
}
|
||||||
@@ -913,23 +979,25 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
recheck = false;
|
recheck = false;
|
||||||
//activate pending event sets that can be activated
|
//activate pending event sets that can be activated
|
||||||
for (int i = pendingEventSets.Count - 1; i >= 0; i--)
|
var pendingSnapshot = _pendingEventSets;
|
||||||
|
for (int i = pendingSnapshot.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
var eventSet = pendingEventSets[i];
|
var eventSet = pendingSnapshot[i];
|
||||||
if (eventCoolDown > 0.0f && !eventSet.IgnoreCoolDown) { continue; }
|
if (eventCoolDown > 0.0f && !eventSet.IgnoreCoolDown) { continue; }
|
||||||
if (currentIntensity > eventThreshold && !eventSet.IgnoreIntensity) { continue; }
|
if (currentIntensity > eventThreshold && !eventSet.IgnoreIntensity) { continue; }
|
||||||
if (!CanStartEventSet(eventSet)) { continue; }
|
if (!CanStartEventSet(eventSet)) { continue; }
|
||||||
|
|
||||||
pendingEventSets.RemoveAt(i);
|
RemovePendingEventSetAt(i);
|
||||||
|
|
||||||
if (selectedEvents.ContainsKey(eventSet))
|
var selectedEventsList = GetSelectedEvents(eventSet);
|
||||||
|
if (selectedEventsList.Count > 0)
|
||||||
{
|
{
|
||||||
//start events in this set
|
//start events in this set
|
||||||
foreach (Event ev in selectedEvents[eventSet])
|
foreach (Event ev in selectedEventsList)
|
||||||
{
|
{
|
||||||
activeEvents.Add(ev);
|
AddActiveEvent(ev);
|
||||||
eventThreshold = settings.DefaultEventThreshold;
|
eventThreshold = settings.DefaultEventThreshold;
|
||||||
if (eventSet.TriggerEventCooldown && selectedEvents[eventSet].Any(e => e.Prefab.TriggerEventCooldown))
|
if (eventSet.TriggerEventCooldown && selectedEventsList.Any(e => e.Prefab.TriggerEventCooldown))
|
||||||
{
|
{
|
||||||
eventCoolDown = settings.EventCooldown;
|
eventCoolDown = settings.EventCooldown;
|
||||||
}
|
}
|
||||||
@@ -937,12 +1005,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
ev.Finished += () =>
|
ev.Finished += () =>
|
||||||
{
|
{
|
||||||
pendingEventSets.Add(eventSet);
|
EnqueueDeferredAction(() =>
|
||||||
CreateEvents(eventSet);
|
|
||||||
foreach (Event newEvent in selectedEvents[eventSet])
|
|
||||||
{
|
{
|
||||||
if (!newEvent.Initialized) { newEvent.Init(eventSet); }
|
AddPendingEventSet(eventSet);
|
||||||
}
|
CreateEvents(eventSet);
|
||||||
|
foreach (Event newEvent in GetSelectedEvents(eventSet))
|
||||||
|
{
|
||||||
|
if (!newEvent.Initialized) { newEvent.Init(eventSet); }
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -951,37 +1022,40 @@ namespace Barotrauma
|
|||||||
//add child event sets to pending
|
//add child event sets to pending
|
||||||
foreach (EventSet childEventSet in eventSet.ChildSets)
|
foreach (EventSet childEventSet in eventSet.ChildSets)
|
||||||
{
|
{
|
||||||
pendingEventSets.Add(childEventSet);
|
AddPendingEventSet(childEventSet);
|
||||||
recheck = true;
|
recheck = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (recheck);
|
} while (recheck);
|
||||||
|
|
||||||
foreach (Event ev in activeEvents)
|
var activeSnapshot = _activeEvents;
|
||||||
|
foreach (Event ev in activeSnapshot)
|
||||||
{
|
{
|
||||||
if (!ev.IsFinished)
|
if (!ev.IsFinished)
|
||||||
{
|
{
|
||||||
ev.Update(deltaTime);
|
ev.Update(deltaTime);
|
||||||
}
|
}
|
||||||
else if (ev.Prefab != null && !finishedEvents.Any(e => e.Prefab == ev.Prefab))
|
else if (ev.Prefab != null && !IsEventFinished(ev))
|
||||||
{
|
{
|
||||||
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
if (level?.LevelData != null && level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||||
{
|
{
|
||||||
if (!level.LevelData.EventHistory.Contains(ev.Prefab.Identifier)) { level.LevelData.EventHistory.Add(ev.Prefab.Identifier); }
|
if (!level.LevelData.EventHistory.Contains(ev.Prefab.Identifier)) { level.LevelData.EventHistory.Add(ev.Prefab.Identifier); }
|
||||||
}
|
}
|
||||||
finishedEvents.Add(ev);
|
AddFinishedEvent(ev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (QueuedEvents.Count > 0)
|
if (QueuedEvents.TryDequeue(out var queuedEvent))
|
||||||
{
|
{
|
||||||
activeEvents.Add(QueuedEvents.Dequeue());
|
AddActiveEvent(queuedEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProcessDeferredActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EntitySpawned(Entity entity)
|
public void EntitySpawned(Entity entity)
|
||||||
{
|
{
|
||||||
foreach (var ev in activeEvents)
|
foreach (var ev in _activeEvents)
|
||||||
{
|
{
|
||||||
if (ev is ScriptedEvent scriptedEvent)
|
if (ev is ScriptedEvent scriptedEvent)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -60,47 +61,48 @@ namespace Barotrauma
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
private static volatile ImmutableDictionary<Identifier, EventPrefab> _allEventPrefabs =
|
||||||
private static readonly Dictionary<Identifier, EventPrefab> AllEventPrefabs = new Dictionary<Identifier, EventPrefab>();
|
ImmutableDictionary<Identifier, EventPrefab>.Empty;
|
||||||
|
|
||||||
public static IEnumerable<EventPrefab> GetAllEventPrefabs()
|
public static IEnumerable<EventPrefab> GetAllEventPrefabs()
|
||||||
{
|
{
|
||||||
return AllEventPrefabs.Values;
|
return _allEventPrefabs.Values;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds all the event prefabs (both "normal prefabs" that exists by themselves, present in <see cref="EventPrefab.Prefabs"/>, and the ones that exists only inside child event sets),
|
/// Finds all the event prefabs (both "normal prefabs" that exists by themselves, present in <see cref="EventPrefab.Prefabs"/>, and the ones that exists only inside child event sets),
|
||||||
/// and adds them to <see cref="AllEventPrefabs"/>.
|
/// and adds them to <see cref="_allEventPrefabs"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void RefreshAllEventPrefabs()
|
public static void RefreshAllEventPrefabs()
|
||||||
{
|
{
|
||||||
AllEventPrefabs.Clear();
|
var builder = ImmutableDictionary.CreateBuilder<Identifier, EventPrefab>();
|
||||||
foreach (var eventPrefab in EventPrefab.Prefabs)
|
foreach (var eventPrefab in EventPrefab.Prefabs)
|
||||||
{
|
{
|
||||||
AllEventPrefabs.TryAdd(eventPrefab.Identifier, eventPrefab);
|
builder.TryAdd(eventPrefab.Identifier, eventPrefab);
|
||||||
}
|
}
|
||||||
foreach (var eventSet in Prefabs)
|
foreach (var eventSet in Prefabs)
|
||||||
{
|
{
|
||||||
AddChildEventPrefabs(eventSet);
|
AddChildEventPrefabs(eventSet, builder);
|
||||||
}
|
}
|
||||||
|
Interlocked.Exchange(ref _allEventPrefabs, builder.ToImmutable());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void AddChildEventPrefabs(EventSet set)
|
private static void AddChildEventPrefabs(EventSet set, ImmutableDictionary<Identifier, EventPrefab>.Builder builder)
|
||||||
{
|
{
|
||||||
foreach (var subEventPrefabs in set.EventPrefabs)
|
foreach (var subEventPrefabs in set.EventPrefabs)
|
||||||
{
|
{
|
||||||
foreach (var eventPrefab in subEventPrefabs.EventPrefabs)
|
foreach (var eventPrefab in subEventPrefabs.EventPrefabs)
|
||||||
{
|
{
|
||||||
AllEventPrefabs.TryAdd(eventPrefab.Identifier, eventPrefab);
|
builder.TryAdd(eventPrefab.Identifier, eventPrefab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var childSet in set.ChildSets) { AddChildEventPrefabs(childSet); }
|
foreach (var childSet in set.ChildSets) { AddChildEventPrefabs(childSet, builder); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static EventPrefab GetEventPrefab(Identifier identifier)
|
public static EventPrefab GetEventPrefab(Identifier identifier)
|
||||||
{
|
{
|
||||||
return AllEventPrefabs.GetValueOrDefault(identifier);
|
return _allEventPrefabs.GetValueOrDefault(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void InitEventSpecific(EventSet parentSet)
|
protected override void InitEventSpecific(EventSet parentSet)
|
||||||
{
|
{
|
||||||
var matchingItems = Item.ItemList.FindAll(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier));
|
var matchingItems = Item.ItemList.Where(i => i.Condition > 0.0f && targetItemIdentifiers.Contains(i.Prefab.Identifier)).ToList();
|
||||||
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient);
|
int itemAmount = Rand.Range(minItemAmount, maxItemAmount, Rand.RandSync.ServerAndClient);
|
||||||
for (int i = 0; i < itemAmount; i++)
|
for (int i = 0; i < itemAmount; i++)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!itemTag.IsEmpty)
|
if (!itemTag.IsEmpty)
|
||||||
{
|
{
|
||||||
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
|
var itemsToDestroy = Item.ItemList.Where(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag)).ToList();
|
||||||
if (!itemsToDestroy.Any())
|
if (!itemsToDestroy.Any())
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".",
|
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".",
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
destructibleItems.Clear();
|
destructibleItems.Clear();
|
||||||
destructibleItems.AddRange(Item.ItemList.FindAll(it => it.HasTag(destructibleItemTag)));
|
destructibleItems.AddRange(Item.ItemList.Where(it => it.HasTag(destructibleItemTag)));
|
||||||
if (destructibleItems.None())
|
if (destructibleItems.None())
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".",
|
DebugConsole.ThrowError($"Error in end mission \"{Prefab.Identifier}\". Could not find any destructible items with the tag \"{spawnPointTag}\".",
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!IsClient)
|
if (!IsClient)
|
||||||
{
|
{
|
||||||
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList, false);
|
PathFinder pathFinder = new PathFinder(WayPoint.WayPointList.ToList(), false);
|
||||||
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
|
var path = pathFinder.FindPath(ConvertUnits.ToSimUnits(patrolPos), ConvertUnits.ToSimUnits(preferredSpawnPos));
|
||||||
if (!path.Unreachable)
|
if (!path.Unreachable)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.DeconstructItems.Add(stackedItem);
|
Item.MarkForDeconstruction(stackedItem);
|
||||||
}
|
}
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
HintManager.OnItemMarkedForDeconstruction(order.OrderGiver);
|
||||||
@@ -138,7 +138,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (var stackedItem in item.GetStackedItems())
|
foreach (var stackedItem in item.GetStackedItems())
|
||||||
{
|
{
|
||||||
Item.DeconstructItems.Remove(stackedItem);
|
Item.UnmarkForDeconstruction(stackedItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1089,7 +1089,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
//Clear the grids to allow for garbage collection
|
//Clear the grids to allow for garbage collection
|
||||||
Powered.Grids.Clear();
|
Powered.Grids.Clear();
|
||||||
Powered.ChangedConnections.Clear();
|
Powered.ClearChangedConnections();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1146,6 +1146,7 @@ namespace Barotrauma
|
|||||||
EventManager?.EndRound();
|
EventManager?.EndRound();
|
||||||
StatusEffect.StopAll();
|
StatusEffect.StopAll();
|
||||||
AfflictionPrefab.ClearAllEffects();
|
AfflictionPrefab.ClearAllEffects();
|
||||||
|
PhysicsBodyQueue.Clear();
|
||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using FarseerPhysics.Dynamics;
|
|||||||
using FarseerPhysics.Dynamics.Joints;
|
using FarseerPhysics.Dynamics.Joints;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
@@ -24,11 +25,8 @@ namespace Barotrauma.Items.Components
|
|||||||
Right
|
Right
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly List<DockingPort> list = new List<DockingPort>();
|
private static readonly ConcurrentDictionary<DockingPort, byte> _dockingPortDict = new ConcurrentDictionary<DockingPort, byte>();
|
||||||
public static IEnumerable<DockingPort> List
|
public static IEnumerable<DockingPort> List => _dockingPortDict.Keys;
|
||||||
{
|
|
||||||
get { return list; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private Sprite overlaySprite;
|
private Sprite overlaySprite;
|
||||||
private float dockingState;
|
private float dockingState;
|
||||||
@@ -168,7 +166,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
|
|
||||||
list.Add(this);
|
_dockingPortDict.TryAdd(this, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void FlipX(bool relativeToSub)
|
public override void FlipX(bool relativeToSub)
|
||||||
@@ -200,7 +198,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
float closestDist = float.MaxValue;
|
float closestDist = float.MaxValue;
|
||||||
DockingPort closestPort = null;
|
DockingPort closestPort = null;
|
||||||
foreach (DockingPort port in list)
|
foreach (DockingPort port in List)
|
||||||
{
|
{
|
||||||
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
|
if (port == this || port.item.Submarine == item.Submarine || port.IsHorizontal != IsHorizontal) { continue; }
|
||||||
float xDist = Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X);
|
float xDist = Math.Abs(port.item.WorldPosition.X - item.WorldPosition.X);
|
||||||
@@ -532,8 +530,8 @@ namespace Barotrauma.Items.Components
|
|||||||
wire.TryConnect(recipient, addNode: false);
|
wire.TryConnect(recipient, addNode: false);
|
||||||
|
|
||||||
//Flag connections to be updated
|
//Flag connections to be updated
|
||||||
Powered.ChangedConnections.Add(powerConnection);
|
Powered.MarkConnectionChanged(powerConnection);
|
||||||
Powered.ChangedConnections.Add(recipient);
|
Powered.MarkConnectionChanged(recipient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateDoorBody()
|
private void CreateDoorBody()
|
||||||
@@ -1007,7 +1005,7 @@ namespace Barotrauma.Items.Components
|
|||||||
Connection powerConnection = Item.Connections.Find(c => c.IsPower);
|
Connection powerConnection = Item.Connections.Find(c => c.IsPower);
|
||||||
if (powerConnection != null)
|
if (powerConnection != null)
|
||||||
{
|
{
|
||||||
Powered.ChangedConnections.Add(powerConnection);
|
Powered.MarkConnectionChanged(powerConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doorBody != null)
|
if (doorBody != null)
|
||||||
@@ -1151,7 +1149,7 @@ namespace Barotrauma.Items.Components
|
|||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
list.Remove(this);
|
_dockingPortDict.TryRemove(this, out _);
|
||||||
hulls[0]?.Remove(); hulls[0] = null;
|
hulls[0]?.Remove(); hulls[0] = null;
|
||||||
hulls[1]?.Remove(); hulls[1] = null;
|
hulls[1]?.Remove(); hulls[1] = null;
|
||||||
gap?.Remove(); gap = null;
|
gap?.Remove(); gap = null;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using FarseerPhysics.Dynamics;
|
using FarseerPhysics.Dynamics;
|
||||||
@@ -14,9 +15,9 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
partial class Door : Pickable, IDrawableComponent, IServerSerializable
|
||||||
{
|
{
|
||||||
private static readonly HashSet<Door> doorList = new HashSet<Door>();
|
private static readonly ConcurrentDictionary<Door, byte> _doorDict = new ConcurrentDictionary<Door, byte>();
|
||||||
|
|
||||||
public static IReadOnlyCollection<Door> DoorList { get { return doorList; } }
|
public static ICollection<Door> DoorList => _doorDict.Keys;
|
||||||
|
|
||||||
private Gap linkedGap;
|
private Gap linkedGap;
|
||||||
private bool isOpen;
|
private bool isOpen;
|
||||||
@@ -277,7 +278,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
IsActive = true;
|
IsActive = true;
|
||||||
doorList.Add(this);
|
_doorDict.TryAdd(this, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnItemLoaded()
|
public override void OnItemLoaded()
|
||||||
@@ -313,13 +314,21 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
public override void Move(Vector2 amount, bool ignoreContacts = false)
|
||||||
{
|
{
|
||||||
if (ignoreContacts)
|
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
||||||
|
if (Body != null)
|
||||||
{
|
{
|
||||||
Body?.SetTransformIgnoreContacts(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
var capturedBody = Body;
|
||||||
}
|
var capturedNewPos = Body.SimPosition + ConvertUnits.ToSimUnits(amount);
|
||||||
else
|
if (ignoreContacts)
|
||||||
{
|
{
|
||||||
Body?.SetTransform(Body.SimPosition + ConvertUnits.ToSimUnits(amount), 0.0f);
|
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
||||||
|
capturedBody.SetTransformIgnoreContacts(capturedNewPos, 0.0f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PhysicsBodyQueue.ExecuteOrDefer(() =>
|
||||||
|
capturedBody.SetTransform(capturedNewPos, 0.0f));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
UpdateConvexHulls();
|
UpdateConvexHulls();
|
||||||
@@ -669,7 +678,7 @@ namespace Barotrauma.Items.Components
|
|||||||
convexHull2?.Remove();
|
convexHull2?.Remove();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
doorList.Remove(this);
|
_doorDict.TryRemove(this, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckSubmarinesInDoorWay()
|
private bool CheckSubmarinesInDoorWay()
|
||||||
@@ -785,13 +794,19 @@ namespace Barotrauma.Items.Components
|
|||||||
//immediately teleport it to the correct side
|
//immediately teleport it to the correct side
|
||||||
if (Math.Sign(diff) != dir)
|
if (Math.Sign(diff) != dir)
|
||||||
{
|
{
|
||||||
|
// Defer physics operation if in parallel context (Farseer is not thread-safe)
|
||||||
|
var capturedBody = body;
|
||||||
if (IsHorizontal)
|
if (IsHorizontal)
|
||||||
{
|
{
|
||||||
body.SetTransformIgnoreContacts(new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f), body.Rotation);
|
Vector2 newPos = new Vector2(body.SimPosition.X, item.SimPosition.Y + dir * doorRectSimSize.Y * 2.0f);
|
||||||
|
float rotation = body.Rotation;
|
||||||
|
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(newPos, rotation));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
body.SetTransformIgnoreContacts(new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y), body.Rotation);
|
Vector2 newPos = new Vector2(item.SimPosition.X + dir * doorRectSimSize.X * 1.2f, body.SimPosition.Y);
|
||||||
|
float rotation = body.Rotation;
|
||||||
|
PhysicsBodyQueue.ExecuteOrDefer(() => capturedBody.SetTransformIgnoreContacts(newPos, rotation));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Barotrauma.Networking;
|
|||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -10,11 +11,8 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class ElectricalDischarger : Powered, IServerSerializable
|
partial class ElectricalDischarger : Powered, IServerSerializable
|
||||||
{
|
{
|
||||||
private static readonly List<ElectricalDischarger> list = new List<ElectricalDischarger>();
|
private static readonly ConcurrentDictionary<ElectricalDischarger, byte> _dischargerDict = new ConcurrentDictionary<ElectricalDischarger, byte>();
|
||||||
public static IEnumerable<ElectricalDischarger> List
|
public static IEnumerable<ElectricalDischarger> List => _dischargerDict.Keys;
|
||||||
{
|
|
||||||
get { return list; }
|
|
||||||
}
|
|
||||||
|
|
||||||
const int MaxNodes = 100;
|
const int MaxNodes = 100;
|
||||||
const float MaxNodeDistance = 150.0f;
|
const float MaxNodeDistance = 150.0f;
|
||||||
@@ -115,7 +113,7 @@ namespace Barotrauma.Items.Components
|
|||||||
public ElectricalDischarger(Item item, ContentXElement element) :
|
public ElectricalDischarger(Item item, ContentXElement element) :
|
||||||
base(item, element)
|
base(item, element)
|
||||||
{
|
{
|
||||||
list.Add(this);
|
_dischargerDict.TryAdd(this, 0);
|
||||||
|
|
||||||
foreach (var subElement in element.Elements())
|
foreach (var subElement in element.Elements())
|
||||||
{
|
{
|
||||||
@@ -604,7 +602,7 @@ namespace Barotrauma.Items.Components
|
|||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
list.Remove(this);
|
_dischargerDict.TryRemove(this, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user