diff --git a/Barotrauma/BarotraumaClient/ClientCode.projitems b/Barotrauma/BarotraumaClient/ClientCode.projitems
index 3864fe96e..a5cc964a4 100644
--- a/Barotrauma/BarotraumaClient/ClientCode.projitems
+++ b/Barotrauma/BarotraumaClient/ClientCode.projitems
@@ -214,6 +214,9 @@
+
+ Never
+
diff --git a/Barotrauma/BarotraumaClient/Content/Content.mgcb b/Barotrauma/BarotraumaClient/Content/Content.mgcb
deleted file mode 100644
index b23f45db0..000000000
--- a/Barotrauma/BarotraumaClient/Content/Content.mgcb
+++ /dev/null
@@ -1,51 +0,0 @@
-
-#----------------------------- Global Properties ----------------------------#
-
-/outputDir:bin/$(Platform)
-/intermediateDir:obj/$(Platform)
-/platform:Windows
-/config:
-/profile:Reach
-/compress:False
-
-#-------------------------------- References --------------------------------#
-
-
-#---------------------------------- Content ---------------------------------#
-
-#begin Effects/blurshader.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/blurshader.fx
-
-#begin Effects/damageshader.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/damageshader.fx
-
-#begin Effects/deformshader.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/deformshader.fx
-
-#begin Effects/losshader.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/losshader.fx
-
-#begin Effects/postprocess.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/postprocess.fx
-
-#begin Effects/watershader.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/watershader.fx
-
diff --git a/Barotrauma/BarotraumaClient/Content/Content_opengl.mgcb b/Barotrauma/BarotraumaClient/Content/Content_opengl.mgcb
deleted file mode 100644
index d89dd6e4e..000000000
--- a/Barotrauma/BarotraumaClient/Content/Content_opengl.mgcb
+++ /dev/null
@@ -1,51 +0,0 @@
-
-#----------------------------- Global Properties ----------------------------#
-
-/outputDir:bin/$(Platform)
-/intermediateDir:obj/$(Platform)
-/platform:DesktopGL
-/config:
-/profile:Reach
-/compress:False
-
-#-------------------------------- References --------------------------------#
-
-
-#---------------------------------- Content ---------------------------------#
-
-#begin Effects/blurshader_opengl.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/blurshader_opengl.fx
-
-#begin Effects/damageshader_opengl.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/damageshader_opengl.fx
-
-#begin Effects/deformshader_opengl.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/deformshader_opengl.fx
-
-#begin Effects/losshader_opengl.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/losshader_opengl.fx
-
-#begin Effects/postprocess_opengl.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/postprocess_opengl.fx
-
-#begin Effects/watershader_opengl.fx
-/importer:EffectImporter
-/processor:EffectProcessor
-/processorParam:DebugMode=Auto
-/build:Effects/watershader_opengl.fx
-
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/deformshader.xnb b/Barotrauma/BarotraumaClient/Content/Effects/deformshader.xnb
index d40fbb220..35b0ae81b 100644
Binary files a/Barotrauma/BarotraumaClient/Content/Effects/deformshader.xnb and b/Barotrauma/BarotraumaClient/Content/Effects/deformshader.xnb differ
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.xnb b/Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.xnb
index 8d926c6ea..15bed2a78 100644
Binary files a/Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.xnb and b/Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.xnb differ
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/solidcolor.xnb b/Barotrauma/BarotraumaClient/Content/Effects/solidcolor.xnb
new file mode 100644
index 000000000..8d2819b44
Binary files /dev/null and b/Barotrauma/BarotraumaClient/Content/Effects/solidcolor.xnb differ
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/solidcolor_opengl.xnb b/Barotrauma/BarotraumaClient/Content/Effects/solidcolor_opengl.xnb
new file mode 100644
index 000000000..efbff6947
Binary files /dev/null and b/Barotrauma/BarotraumaClient/Content/Effects/solidcolor_opengl.xnb differ
diff --git a/Barotrauma/BarotraumaClient/LinuxClient.csproj b/Barotrauma/BarotraumaClient/LinuxClient.csproj
index fe0c40067..5bfa817fa 100644
--- a/Barotrauma/BarotraumaClient/LinuxClient.csproj
+++ b/Barotrauma/BarotraumaClient/LinuxClient.csproj
@@ -152,7 +152,14 @@
-
+
+
+
+
+
+
+
+
PreserveNewest
diff --git a/Barotrauma/BarotraumaClient/MacClient.csproj b/Barotrauma/BarotraumaClient/MacClient.csproj
index a12706abe..5fbe31943 100644
--- a/Barotrauma/BarotraumaClient/MacClient.csproj
+++ b/Barotrauma/BarotraumaClient/MacClient.csproj
@@ -153,6 +153,13 @@
+
+
+
+
+
+
+
PreserveNewest
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/blurshader.fx b/Barotrauma/BarotraumaClient/Shaders/blurshader.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/blurshader.fx
rename to Barotrauma/BarotraumaClient/Shaders/blurshader.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/blurshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/blurshader_opengl.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/blurshader_opengl.fx
rename to Barotrauma/BarotraumaClient/Shaders/blurshader_opengl.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/damageshader.fx b/Barotrauma/BarotraumaClient/Shaders/damageshader.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/damageshader.fx
rename to Barotrauma/BarotraumaClient/Shaders/damageshader.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/damageshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/damageshader_opengl.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/damageshader_opengl.fx
rename to Barotrauma/BarotraumaClient/Shaders/damageshader_opengl.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/deformshader.fx b/Barotrauma/BarotraumaClient/Shaders/deformshader.fx
similarity index 87%
rename from Barotrauma/BarotraumaClient/Content/Effects/deformshader.fx
rename to Barotrauma/BarotraumaClient/Shaders/deformshader.fx
index bf621efe0..dcf54addd 100644
--- a/Barotrauma/BarotraumaClient/Content/Effects/deformshader.fx
+++ b/Barotrauma/BarotraumaClient/Shaders/deformshader.fx
@@ -14,6 +14,7 @@ float2 uvTopLeft;
float2 uvBottomRight;
float4 tintColor;
+float4 solidColor;
struct VertexShaderInput
{
@@ -74,6 +75,11 @@ float4 mainPS(VertexShaderOutput input) : COLOR
return xTexture.Sample(TextureSampler, input.TexCoords) * input.Color;
}
+float4 solidColorPS(VertexShaderOutput input) : COLOR
+{
+ return solidColor * xTexture.Sample(TextureSampler, input.TexCoords).a;
+}
+
technique DeformShader
{
pass Pass1
@@ -81,4 +87,13 @@ technique DeformShader
VertexShader = compile vs_4_0_level_9_1 mainVS();
PixelShader = compile ps_4_0_level_9_1 mainPS();
}
+}
+
+technique DeformShaderSolidColor
+{
+ pass Pass1
+ {
+ VertexShader = compile vs_4_0_level_9_1 mainVS();
+ PixelShader = compile ps_4_0_level_9_1 solidColorPS();
+ }
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/deformshader_opengl.fx
similarity index 87%
rename from Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.fx
rename to Barotrauma/BarotraumaClient/Shaders/deformshader_opengl.fx
index fbf640ede..c77904ffe 100644
--- a/Barotrauma/BarotraumaClient/Content/Effects/deformshader_opengl.fx
+++ b/Barotrauma/BarotraumaClient/Shaders/deformshader_opengl.fx
@@ -14,6 +14,7 @@ float2 uvTopLeft;
float2 uvBottomRight;
float4 tintColor;
+float4 solidColor;
struct VertexShaderInput
{
@@ -74,6 +75,11 @@ float4 mainPS(VertexShaderOutput input) : COLOR
return xTexture.Sample(TextureSampler, input.TexCoords) * input.Color;
}
+float4 solidColorPS(VertexShaderOutput input) : COLOR
+{
+ return solidColor * xTexture.Sample(TextureSampler, input.TexCoords).a;
+}
+
technique DeformShader
{
pass Pass1
@@ -81,4 +87,13 @@ technique DeformShader
VertexShader = compile vs_3_0 mainVS();
PixelShader = compile ps_3_0 mainPS();
}
+}
+
+technique DeformShaderSolidColor
+{
+ pass Pass1
+ {
+ VertexShader = compile vs_3_0 mainVS();
+ PixelShader = compile ps_3_0 solidColorPS();
+ }
}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/losshader.fx b/Barotrauma/BarotraumaClient/Shaders/losshader.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/losshader.fx
rename to Barotrauma/BarotraumaClient/Shaders/losshader.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/losshader_opengl.fx
rename to Barotrauma/BarotraumaClient/Shaders/losshader_opengl.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/postprocess.fx b/Barotrauma/BarotraumaClient/Shaders/postprocess.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/postprocess.fx
rename to Barotrauma/BarotraumaClient/Shaders/postprocess.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/postprocess_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/postprocess_opengl.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/postprocess_opengl.fx
rename to Barotrauma/BarotraumaClient/Shaders/postprocess_opengl.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/watershader.fx b/Barotrauma/BarotraumaClient/Shaders/watershader.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/watershader.fx
rename to Barotrauma/BarotraumaClient/Shaders/watershader.fx
diff --git a/Barotrauma/BarotraumaClient/Content/Effects/watershader_opengl.fx b/Barotrauma/BarotraumaClient/Shaders/watershader_opengl.fx
similarity index 100%
rename from Barotrauma/BarotraumaClient/Content/Effects/watershader_opengl.fx
rename to Barotrauma/BarotraumaClient/Shaders/watershader_opengl.fx
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs
index 9f108885f..b588b93a4 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/AI/HumanAIController.cs
@@ -87,52 +87,5 @@ namespace Barotrauma
// GUI.DrawLine(spriteBatch, pos, new Vector2(Character.CursorWorldPosition.X, -Character.CursorWorldPosition.Y), Color.Yellow, width: 4);
//}
}
-
- //TODO: move this to the shared project, otherwise bots won't be able to report things in multiplayer
- partial void ReportProblems()
- {
- if (GameMain.Client != null) return;
-
- Order newOrder = null;
- if (Character.CurrentHull != null)
- {
- if (Character.CurrentHull.FireSources.Count > 0)
- {
- var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportfire");
- newOrder = new Order(orderPrefab, Character.CurrentHull, null);
- }
-
- if (Character.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.ConnectedDoor == null && g.Open > 0.0f))
- {
- var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportbreach");
- newOrder = new Order(orderPrefab, Character.CurrentHull, null);
- }
-
- foreach (Character c in Character.CharacterList)
- {
- if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
- (c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
- {
- var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
- newOrder = new Order(orderPrefab, Character.CurrentHull, null);
- }
- }
- }
-
- if (Character.CurrentHull != null && (Character.Bleeding > 1.0f || Character.Vitality < Character.MaxVitality * 0.1f))
- {
- var orderPrefab = Order.PrefabList.Find(o => o.AITag == "requestfirstaid");
- newOrder = new Order(orderPrefab, Character.CurrentHull, null);
- }
-
- if (newOrder != null)
- {
- if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
- {
- Character.Speak(
- newOrder.GetChatMessage("", Character.CurrentHull?.RoomName, givingOrderToSelf: false), ChatMessageType.Order);
- }
- }
- }
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs
index 390c10936..615423b53 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/Character.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/Character.cs
@@ -338,7 +338,7 @@ namespace Barotrauma
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
Item closestItem = null;
- float closestItemDistance = aimAssistAmount;
+ float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
foreach (MapEntity entity in entityList)
{
if (!(entity is Item item))
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs
index 7c5967485..86c88a3e0 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterHUD.cs
@@ -1,4 +1,5 @@
using Barotrauma.Items.Components;
+using FarseerPhysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -136,10 +137,14 @@ namespace Barotrauma
brokenItemsCheckTimer = 1.0f;
foreach (Item item in Item.ItemList)
{
- if (item.CurrentHull == character.CurrentHull && item.Repairables.Any(r => item.Condition < r.ShowRepairUIThreshold))
+ if (!item.Repairables.Any(r => item.Condition < r.ShowRepairUIThreshold)) { continue; }
+ if (!Submarine.VisibleEntities.Contains(item)) { continue; }
+
+ Vector2 diff = item.WorldPosition - character.WorldPosition;
+ if (Submarine.CheckVisibility(character.SimPosition, character.SimPosition + ConvertUnits.ToSimUnits(diff)) == null)
{
- brokenItems.Add(item);
- }
+ brokenItems.Add(item);
+ }
}
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs
index 983ffb1a5..4aac62ef9 100644
--- a/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaClient/Source/Characters/CharacterNetworking.cs
@@ -123,10 +123,7 @@ namespace Barotrauma
for (int i = 0; i < inputCount; i++)
{
msg.WriteRangedInteger(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
- if (memInput[i].states.HasFlag(InputNetFlags.Aim))
- {
- msg.Write(memInput[i].intAim);
- }
+ msg.Write(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
diff --git a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs
index 81342069c..6345fd8d7 100644
--- a/Barotrauma/BarotraumaClient/Source/DebugConsole.cs
+++ b/Barotrauma/BarotraumaClient/Source/DebugConsole.cs
@@ -436,13 +436,15 @@ namespace Barotrauma
AssignOnExecute("ambientlight", (string[] args) =>
{
- if (Level.Loaded == null)
- {
- ThrowError("Could not set ambient light color (no level loaded).");
- return;
- }
Color color = XMLExtensions.ParseColor(string.Join("", args));
- Level.Loaded.GenerationParams.AmbientLightColor = color;
+ if (Level.Loaded != null)
+ {
+ Level.Loaded.GenerationParams.AmbientLightColor = color;
+ }
+ else
+ {
+ GameMain.LightManager.AmbientLight = color;
+ }
NewMessage("Set ambient light color to " + color + ".", Color.White);
});
AssignRelayToServer("ambientlight", false);
@@ -1050,6 +1052,195 @@ namespace Barotrauma
TextManager.WriteToCSV();
NPCConversation.WriteToCSV();
}));
+
+ commands.Add(new Command("csvtoxml", "csvtoxml [language] -> Converts .csv localization files in Content/NPCConversations & Content/Texts to .xml for use in-game.", (string[] args) =>
+ {
+ if (args.Length == 0) return;
+ LocalizationCSVtoXML.Convert(args[0]);
+ }));
+#endif
+
+ commands.Add(new Command("cleanbuild", "", (string[] args) =>
+ {
+ GameMain.Config.MusicVolume = 0.5f;
+ GameMain.Config.SoundVolume = 0.5f;
+ NewMessage("Music and sound volume set to 0.5", Color.Green);
+
+ commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) =>
+ {
+ float defaultZoom = Screen.Selected.Cam.DefaultZoom;
+ if (args.Length > 0) float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out defaultZoom);
+
+ float zoomSmoothness = Screen.Selected.Cam.ZoomSmoothness;
+ if (args.Length > 1) float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out zoomSmoothness);
+ float moveSmoothness = Screen.Selected.Cam.MoveSmoothness;
+ if (args.Length > 2) float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out moveSmoothness);
+
+ float minZoom = Screen.Selected.Cam.MinZoom;
+ if (args.Length > 3) float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out minZoom);
+ float maxZoom = Screen.Selected.Cam.MaxZoom;
+ if (args.Length > 4) float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out maxZoom);
+
+ Screen.Selected.Cam.DefaultZoom = defaultZoom;
+ Screen.Selected.Cam.ZoomSmoothness = zoomSmoothness;
+ Screen.Selected.Cam.MoveSmoothness = moveSmoothness;
+ Screen.Selected.Cam.MinZoom = minZoom;
+ Screen.Selected.Cam.MaxZoom = maxZoom;
+ }));
+
+ commands.Add(new Command("waterparams", "waterparams [distortionscalex] [distortionscaley] [distortionstrengthx] [distortionstrengthy] [bluramount]: default 0.5 0.5 0.5 0.5 1", (string[] args) =>
+ {
+ float distortScaleX = 0.5f, distortScaleY = 0.5f;
+ float distortStrengthX = 0.5f, distortStrengthY = 0.5f;
+ float blurAmount = 0.0f;
+ if (args.Length > 0) float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out distortScaleX);
+ if (args.Length > 1) float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out distortScaleY);
+ if (args.Length > 2) float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out distortStrengthX);
+ if (args.Length > 3) float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out distortStrengthY);
+ if (args.Length > 4) float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out blurAmount);
+ WaterRenderer.DistortionScale = new Vector2(distortScaleX, distortScaleY);
+ WaterRenderer.DistortionStrength = new Vector2(distortStrengthX, distortStrengthY);
+ WaterRenderer.BlurAmount = blurAmount;
+ }));
+
+
+ commands.Add(new Command("refreshrect", "Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (string[] args) =>
+ {
+ //TODO: maybe do this automatically during loading when possible?
+ if (Screen.Selected == GameMain.SubEditorScreen)
+ {
+ if (!MapEntity.SelectedAny)
+ {
+ ThrowError("You have to select item(s) first!");
+ }
+ else
+ {
+ foreach (var mapEntity in MapEntity.SelectedList)
+ {
+ if (mapEntity is Item item)
+ {
+ item.Rect = new Rectangle(item.Rect.X, item.Rect.Y,
+ (int)(item.Prefab.sprite.size.X * item.Prefab.Scale),
+ (int)(item.Prefab.sprite.size.Y * item.Prefab.Scale));
+ }
+ else if (mapEntity is Structure structure)
+ {
+ if (!structure.ResizeHorizontal)
+ {
+ structure.Rect = new Rectangle(structure.Rect.X, structure.Rect.Y,
+ (int)structure.Prefab.ScaledSize.X,
+ structure.Rect.Height);
+ }
+ if (!structure.ResizeVertical)
+ {
+ structure.Rect = new Rectangle(structure.Rect.X, structure.Rect.Y,
+ structure.Rect.Width,
+ (int)structure.Prefab.ScaledSize.Y);
+ }
+ }
+ }
+ }
+ }
+ }, isCheat: false));
+#endif
+
+ GameMain.Config.SaveNewPlayerConfig();
+
+ commands.Add(new Command("loadtexts", "loadtexts [sourcefile] [destinationfile]: Loads all lines of text from a given .txt file and inserts them sequientially into the elements of an xml file. If the file paths are omitted, EnglishVanilla.txt and EnglishVanilla.xml are used.", (string[] args) =>
+ {
+ string sourcePath = args.Length > 0 ? args[0] : "Content/Texts/EnglishVanilla.txt";
+ string destinationPath = args.Length > 1 ? args[1] : "Content/Texts/EnglishVanilla.xml";
+
+ string[] lines;
+ try
+ {
+ lines = File.ReadAllLines(sourcePath);
+ }
+ catch (Exception e)
+ {
+ ThrowError("Reading the file \"" + sourcePath + "\" failed.", e);
+ return;
+ }
+ var doc = XMLExtensions.TryLoadXml(destinationPath);
+ int i = 0;
+ foreach (XElement element in doc.Root.Elements())
+ {
+ if (i >= lines.Length)
+ {
+ ThrowError("Error while loading texts to the xml file. The xml has more elements than the number of lines in the text file.");
+ return;
+ }
+ element.Value = lines[i];
+ i++;
+ }
+ doc.Save(destinationPath);
+ },
+ () =>
+ {
+ var files = TextManager.GetTextFiles().Select(f => f.Replace("\\", "/"));
+ return new string[][]
+ {
+ files.Where(f => Path.GetExtension(f)==".txt").ToArray(),
+ files.Where(f => Path.GetExtension(f)==".xml").ToArray()
+ };
+ }));
+
+ commands.Add(new Command("updatetextfile", "updatetextfile [sourcefile] [destinationfile]: Inserts all the xml elements that are only present in the source file into the destination file. Can be used to update outdated translation files more easily.", (string[] args) =>
+ {
+ if (args.Length < 2) return;
+ string sourcePath = args[0];
+ string destinationPath = args[1];
+
+ var sourceDoc = XMLExtensions.TryLoadXml(sourcePath);
+ var destinationDoc = XMLExtensions.TryLoadXml(destinationPath);
+
+ XElement destinationElement = destinationDoc.Root.Elements().First();
+ foreach (XElement element in sourceDoc.Root.Elements())
+ {
+ if (destinationDoc.Root.Element(element.Name) == null)
+ {
+ element.Value = "!!!!!!!!!!!!!" + element.Value;
+ destinationElement.AddAfterSelf(element);
+ }
+ XNode nextNode = destinationElement.NextNode;
+ while ((!(nextNode is XElement) || nextNode == element) && nextNode != null) nextNode = nextNode.NextNode;
+ destinationElement = nextNode as XElement;
+ }
+ destinationDoc.Save(destinationPath);
+ },
+ () =>
+ {
+ var files = TextManager.GetTextFiles().Where(f => Path.GetExtension(f) == ".xml").Select(f => f.Replace("\\", "/")).ToArray();
+ return new string[][]
+ {
+ files,
+ files
+ };
+ }));
+
+ commands.Add(new Command("dumpentitytexts", "dumpentitytexts [filepath]: gets the names and descriptions of all entity prefabs and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EntityTexts.txt", (string[] args) =>
+ {
+ string filePath = args.Length > 0 ? args[0] : "Content/Texts/EntityTexts.txt";
+ List lines = new List();
+ foreach (MapEntityPrefab me in MapEntityPrefab.List)
+ {
+ lines.Add("" + me.Name + "" + me.Identifier + ".Name>");
+ lines.Add("" + me.Description + "" + me.Identifier + ".Description>");
+ }
+ File.WriteAllLines(filePath, lines);
+ }));
+#if DEBUG
+ commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) =>
+ {
+ if (args.Length != 1) return;
+ TextManager.CheckForDuplicates(args[0]);
+ }));
+
+ commands.Add(new Command("writetocsv", "Writes the default language (English) to a .csv file.", (string[] args) =>
+ {
+ TextManager.WriteToCSV();
+ NPCConversation.WriteToCSV();
+ }));
#endif
commands.Add(new Command("cleanbuild", "", (string[] args) =>
diff --git a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs
index 7d0daa696..cd8a792c0 100644
--- a/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs
+++ b/Barotrauma/BarotraumaClient/Source/GUI/GUI.cs
@@ -384,12 +384,12 @@ namespace Barotrauma
Cursor.Draw(spriteBatch, PlayerInput.LatestMousePosition);
}
- public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite)
+ public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float blurAmount = 1.0f, float aberrationStrength = 1.0f)
{
double aberrationT = (Timing.TotalTime * 0.5f);
- GameMain.GameScreen.PostProcessEffect.Parameters["blurDistance"].SetValue(0.001f);
+ GameMain.GameScreen.PostProcessEffect.Parameters["blurDistance"].SetValue(0.001f * aberrationStrength);
GameMain.GameScreen.PostProcessEffect.Parameters["chromaticAberrationStrength"].SetValue(new Vector3(-0.025f, -0.01f, -0.05f) *
- (float)(PerlinNoise.CalculatePerlin(aberrationT, aberrationT, 0) + 0.5f));
+ (float)(PerlinNoise.CalculatePerlin(aberrationT, aberrationT, 0) + 0.5f) * aberrationStrength);
GameMain.GameScreen.PostProcessEffect.CurrentTechnique = GameMain.GameScreen.PostProcessEffect.Techniques["BlurChromaticAberration"];
GameMain.GameScreen.PostProcessEffect.CurrentTechnique.Passes[0].Apply();
diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs
index 202dc9965..a5968ce0f 100644
--- a/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs
+++ b/Barotrauma/BarotraumaClient/Source/GameSession/CrewManager.cs
@@ -107,14 +107,37 @@ namespace Barotrauma
};
scrollButtonDown.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipVertically);
- var characterInfo = new CharacterInfo(subElement);
- characterInfos.Add(characterInfo);
- foreach (XElement invElement in subElement.Elements())
+ if (isSinglePlayer)
+ {
+ chatBox = new ChatBox(guiFrame, isSinglePlayer: true)
{
- if (invElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
- characterInfo.InventoryData = invElement;
- break;
- }
+ OnEnterMessage = (textbox, text) =>
+ {
+ if (Character.Controlled == null) { return true; }
+
+ textbox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
+
+ if (!string.IsNullOrWhiteSpace(text))
+ {
+ string msgCommand = ChatMessage.GetChatMessageCommand(text, out string msg);
+ AddSinglePlayerChatMessage(
+ Character.Controlled.Info.Name,
+ msg,
+ ((msgCommand == "r" || msgCommand == "radio") && ChatMessage.CanUseRadio(Character.Controlled)) ? ChatMessageType.Radio : ChatMessageType.Default,
+ Character.Controlled);
+ var headset = GetHeadset(Character.Controlled, true);
+ if (headset != null && headset.CanTransmit())
+ {
+ headset.TransmitSignal(stepsTaken: 0, signal: msg, source: headset.Item, sender: Character.Controlled, sendToChat: false);
+ }
+ }
+ textbox.Deselect();
+ textbox.Text = "";
+ return true;
+ }
+ };
+
+ chatBox.InputBox.OnTextChanged += chatBox.TypingChatMessage;
}
var reports = Order.PrefabList.FindAll(o => o.TargetAllCharacters && o.SymbolSprite != null);
@@ -136,6 +159,7 @@ namespace Barotrauma
{
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
SetCharacterOrder(null, order, null, Character.Controlled);
+ HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull);
return true;
},
UserData = order,
@@ -152,12 +176,14 @@ namespace Barotrauma
Visible = false
};
- var img = new GUIImage(new RectTransform(Vector2.One, btn.RectTransform), order.Prefab.SymbolSprite, scaleToFit: true)
+ var characterInfo = new CharacterInfo(subElement);
+ characterInfos.Add(characterInfo);
+ foreach (XElement invElement in subElement.Elements())
{
- Color = order.Color,
- HoverColor = Color.Lerp(order.Color, Color.White, 0.5f),
- ToolTip = order.Name
- };
+ if (invElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
+ characterInfo.InventoryData = invElement;
+ break;
+ }
}
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
diff --git a/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs b/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs
index fc3e1c8f8..16a57092d 100644
--- a/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs
+++ b/Barotrauma/BarotraumaClient/Source/GameSession/GameSession.cs
@@ -7,10 +7,6 @@ namespace Barotrauma
{
private InfoFrameTab selectedTab;
private GUIButton infoFrame;
- ///
- /// Determines whether the hotkey for the info button was held down in the previous frame.
- ///
- private bool prevInfoKey;
private GUIFrame infoFrameContent;
@@ -19,12 +15,7 @@ namespace Barotrauma
{
get { return roundSummary; }
}
-
- partial void InitProjSpecific()
- {
- prevInfoKey = false;
- }
-
+
private bool ToggleInfoFrame()
{
if (infoFrame == null)
@@ -45,8 +36,7 @@ namespace Barotrauma
int width = 600, height = 400;
infoFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
-
-
+
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.35f), infoFrame.RectTransform, Anchor.Center) { MinSize = new Point(width,height) });
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center), style:null);
@@ -135,10 +125,17 @@ namespace Barotrauma
{
if (GUI.DisableHUD) return;
- if (prevInfoKey != (PlayerInput.KeyDown(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber == null))
+ if (PlayerInput.KeyDown(InputType.InfoTab) &&
+ (GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber is GUIListBox))
+ {
+ if (infoFrame == null)
+ {
+ ToggleInfoFrame();
+ }
+ }
+ else if (infoFrame != null)
{
ToggleInfoFrame();
- prevInfoKey = PlayerInput.KeyDown(InputType.InfoTab);
}
infoFrame?.UpdateManually(deltaTime);
diff --git a/Barotrauma/BarotraumaClient/Source/GameSettings.cs b/Barotrauma/BarotraumaClient/Source/GameSettings.cs
index a111ddc03..00085898f 100644
--- a/Barotrauma/BarotraumaClient/Source/GameSettings.cs
+++ b/Barotrauma/BarotraumaClient/Source/GameSettings.cs
@@ -626,28 +626,6 @@ namespace Barotrauma
new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage"));
- return true;
- };
-
- //spacing
- new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null);
-
- new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonArea.RectTransform, Anchor.BottomLeft),
- TextManager.Get("Cancel"))
- {
- IgnoreLayoutGroups = true,
- OnClicked = (x, y) =>
- {
- if (UnsavedSettings)
- {
- LoadPlayerConfig();
- }
- if (Screen.Selected == GameMain.MainMenuScreen) GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
- GUI.SettingsMenuOpen = false;
- return true;
- }
- };
-
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null);
diff --git a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs
index 94a9eee32..72589ccf5 100644
--- a/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs
+++ b/Barotrauma/BarotraumaClient/Source/Items/Components/ItemComponent.cs
@@ -184,63 +184,6 @@ namespace Barotrauma.Items.Components
}
}
}
-
- public void ApplyTo(RectTransform target)
- {
- if (RelativeOffset.HasValue)
- {
- target.RelativeOffset = RelativeOffset.Value;
- }
- else if (AbsoluteOffset.HasValue)
- {
- target.AbsoluteOffset = AbsoluteOffset.Value;
- }
- if (RelativeSize.HasValue)
- {
- target.RelativeSize = RelativeSize.Value;
- }
- else if (AbsoluteSize.HasValue)
- {
- target.NonScaledSize = AbsoluteSize.Value;
- }
- if (Anchor.HasValue)
- {
- target.Anchor = Anchor.Value;
- }
- if (Pivot.HasValue)
- {
- target.Pivot = Pivot.Value;
- }
- else
- {
- target.Pivot = RectTransform.MatchPivotToAnchor(target.Anchor);
- }
- target.RecalculateChildren(true, true);
- }
- }
-
- public GUIFrame GuiFrame { get; protected set; }
-
- [Serialize(false, false)]
- public bool AllowUIOverlap
- {
- get;
- set;
- }
-
- private ItemComponent linkToUIComponent;
- [Serialize("", false)]
- public string LinkUIToComponent
- {
- get;
- set;
- }
-
- [Serialize(0, false)]
- public int HudPriority
- {
- get;
- private set;
}
private bool shouldMuffleLooping;
diff --git a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs
index 96adb2e97..44c39061f 100644
--- a/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs
+++ b/Barotrauma/BarotraumaClient/Source/Items/Inventory.cs
@@ -598,7 +598,7 @@ namespace Barotrauma
if (selectedSlot == null)
{
- draggingItem.Drop();
+ draggingItem.Drop(Character.Controlled);
GUI.PlayUISound(GUISoundType.DropItem);
}
else if (selectedSlot.ParentInventory.Items[selectedSlot.SlotIndex] != draggingItem)
@@ -954,7 +954,7 @@ namespace Barotrauma
{
if (receivedItemIDs[i] == 0 || (Entity.FindEntityByID(receivedItemIDs[i]) as Item != Items[i]))
{
- if (Items[i] != null) Items[i].Drop();
+ if (Items[i] != null) Items[i].Drop(null);
System.Diagnostics.Debug.Assert(Items[i] == null);
}
}
diff --git a/Barotrauma/BarotraumaClient/Source/Items/Item.cs b/Barotrauma/BarotraumaClient/Source/Items/Item.cs
index 9c7b97434..2f1a0c511 100644
--- a/Barotrauma/BarotraumaClient/Source/Items/Item.cs
+++ b/Barotrauma/BarotraumaClient/Source/Items/Item.cs
@@ -849,6 +849,11 @@ namespace Barotrauma
{
if (GameMain.Client == null) { return; }
+ if (parentInventory != null || body == null || !body.Enabled || Removed)
+ {
+ return;
+ }
+
Vector2 newVelocity = body.LinearVelocity;
Vector2 newPosition = body.SimPosition;
float newAngularVelocity = body.AngularVelocity;
diff --git a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
index 1abfdefb6..98b7cd3bc 100644
--- a/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
+++ b/Barotrauma/BarotraumaClient/Source/Networking/GameClient.cs
@@ -202,6 +202,12 @@ namespace Barotrauma.Networking
private void ConnectToServer(string hostIP)
{
+ chatBox.InputBox.Enabled = false;
+ if (GameMain.NetLobbyScreen?.TextBox != null)
+ {
+ GameMain.NetLobbyScreen.TextBox.Enabled = false;
+ }
+
string[] address = hostIP.Split(':');
if (address.Length == 1)
{
@@ -259,7 +265,11 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Couldn't connect to " + hostIP + ". Error message: " + e.Message);
Disconnect();
-
+ chatBox.InputBox.Enabled = true;
+ if (GameMain.NetLobbyScreen?.TextBox != null)
+ {
+ GameMain.NetLobbyScreen.TextBox.Enabled = true;
+ }
GameMain.ServerListScreen.Select();
return;
}
@@ -530,6 +540,10 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.Select();
}
connected = true;
+ if (GameMain.NetLobbyScreen?.TextBox != null)
+ {
+ GameMain.NetLobbyScreen.TextBox.Enabled = true;
+ }
}
yield return CoroutineStatus.Success;
diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs
index 258eb60de..12d87cf51 100644
--- a/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs
+++ b/Barotrauma/BarotraumaClient/Source/Screens/CampaignUI.cs
@@ -118,7 +118,18 @@ namespace Barotrauma
CanBeFocused = false
};
- characterList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.95f), tabs[(int)Tab.Crew].RectTransform, Anchor.Center))
+ var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), tabs[(int)Tab.Crew].RectTransform, Anchor.Center))
+ {
+ Stretch = true,
+ RelativeSpacing = 0.02f
+ };
+
+ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), crewContent.RectTransform), "", font: GUI.LargeFont)
+ {
+ TextGetter = GetMoney
+ };
+
+ characterList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.9f), crewContent.RectTransform))
{
OnSelected = SelectCharacter
};
diff --git a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs
index 523b82cc7..ce3420604 100644
--- a/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs
+++ b/Barotrauma/BarotraumaClient/Source/Screens/CharacterEditorScreen.cs
@@ -438,6 +438,43 @@ namespace Barotrauma
UpdateSourceRect(limb, newRect);
}
}
+ UpdateJointCreation();
+ if (PlayerInput.KeyHit(Keys.Left))
+ {
+ foreach (var limb in selectedLimbs)
+ {
+ var newRect = limb.ActiveSprite.SourceRect;
+ newRect.X--;
+ UpdateSourceRect(limb, newRect);
+ }
+ }
+ if (PlayerInput.KeyHit(Keys.Right))
+ {
+ foreach (var limb in selectedLimbs)
+ {
+ var newRect = limb.ActiveSprite.SourceRect;
+ newRect.X++;
+ UpdateSourceRect(limb, newRect);
+ }
+ }
+ if (PlayerInput.KeyHit(Keys.Down))
+ {
+ foreach (var limb in selectedLimbs)
+ {
+ var newRect = limb.ActiveSprite.SourceRect;
+ newRect.Y++;
+ UpdateSourceRect(limb, newRect);
+ }
+ }
+ if (PlayerInput.KeyHit(Keys.Up))
+ {
+ foreach (var limb in selectedLimbs)
+ {
+ var newRect = limb.ActiveSprite.SourceRect;
+ newRect.Y--;
+ UpdateSourceRect(limb, newRect);
+ }
+ }
}
if (!isFreezed)
{
diff --git a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs
index ca9b6c880..bb123c052 100644
--- a/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs
+++ b/Barotrauma/BarotraumaClient/Source/Screens/MainMenuScreen.cs
@@ -38,9 +38,18 @@ namespace Barotrauma
#region Creation
public MainMenuScreen(GameMain game)
{
+ new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight)
+ { RelativeOffset = new Vector2(0.05f, 0.05f), AbsoluteOffset = new Point(-5, -5) },
+ style: "TitleText")
+ {
+ Color = Color.Black * 0.5f,
+ CanBeFocused = false
+ };
+ new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.05f, 0.05f) },
+ style: "TitleText");
+
buttonsParent = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.85f), parent: Frame.RectTransform, anchor: Anchor.BottomLeft, pivot: Pivot.BottomLeft)
{
- RelativeOffset = new Vector2(0, 0),
AbsoluteOffset = new Point(50, 0)
})
{
@@ -66,7 +75,7 @@ namespace Barotrauma
var campaignButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: campaignNavigation.RectTransform), style: "MainMenuGUIFrame");
- var campaignList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: campaignButtons.RectTransform))
+ var campaignList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: campaignButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
@@ -112,7 +121,7 @@ namespace Barotrauma
var multiplayerButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: multiplayerNavigation.RectTransform), style: "MainMenuGUIFrame");
- var multiplayerList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: multiplayerButtons.RectTransform))
+ var multiplayerList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: multiplayerButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
@@ -157,7 +166,7 @@ namespace Barotrauma
var customizeButtons = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent: customizeNavigation.RectTransform), style: "MainMenuGUIFrame");
- var customizeList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), parent: customizeButtons.RectTransform))
+ var customizeList = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.2f), parent: customizeButtons.RectTransform))
{
Stretch = false,
RelativeSpacing = 0.035f
@@ -673,7 +682,12 @@ namespace Barotrauma
backgroundSprite = (LocationType.List.Where(l => l.UseInMainMenu).GetRandom()).GetPortrait(0);
}
- if (backgroundSprite != null) { GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite); }
+ if (backgroundSprite != null)
+ {
+ GUI.DrawBackgroundSprite(spriteBatch, backgroundSprite,
+ blurAmount: 0.0f,
+ aberrationStrength: 0.0f);
+ }
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
diff --git a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs
index 4469d6211..022395eb3 100644
--- a/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs
+++ b/Barotrauma/BarotraumaClient/Source/Screens/SubEditorScreen.cs
@@ -625,8 +625,7 @@ namespace Barotrauma
{
foreach (Item item in itemInventory.Items)
{
- if (item == null) continue;
- item.Drop();
+ item?.Drop(null);
}
}
else // If current screen is not subeditor, delete anyway to avoid lingering objects
@@ -671,8 +670,7 @@ namespace Barotrauma
{
foreach (Item item in itemInventory.Items)
{
- if (item == null) continue;
- item.Drop();
+ item?.Drop(null);
}
}
else // If current screen is not subeditor, delete anyway to avoid lingering objects
@@ -703,8 +701,7 @@ namespace Barotrauma
{
foreach (Item item in itemInventory.Items)
{
- if (item == null) continue;
- item.Drop();
+ item?.Drop(null);
}
}
else // If current screen is not subeditor, delete anyway to avoid lingering objects
@@ -1457,7 +1454,7 @@ namespace Barotrauma
Item existingWire = dummyCharacter.SelectedItems.FirstOrDefault(i => i != null && i.Prefab == userData as ItemPrefab);
if (existingWire != null)
{
- existingWire.Drop();
+ existingWire.Drop(null);
existingWire.Remove();
return false;
}
@@ -1470,7 +1467,7 @@ namespace Barotrauma
existingWire = dummyCharacter.Inventory.Items[slotIndex];
if (existingWire != null && existingWire.Prefab != userData as ItemPrefab)
{
- existingWire.Drop();
+ existingWire.Drop(null);
existingWire.Remove();
}
diff --git a/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs b/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs
index e299e79cd..4256482ea 100644
--- a/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs
+++ b/Barotrauma/BarotraumaClient/Source/Sounds/SoundManager.cs
@@ -109,6 +109,12 @@ namespace Barotrauma.Sounds
{
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
}
+ public int UniqueLoadedSoundCount
+ {
+ get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
+ }
+
+ private Dictionary> categoryModifiers;
private Dictionary> categoryModifiers;
@@ -418,6 +424,10 @@ namespace Barotrauma.Sounds
{
categoryModifiers[category].Second = muffle;
}
+ else
+ {
+ categoryModifiers[category].Second = muffle;
+ }
}
for (int i = 0; i < playingChannels.Length; i++)
diff --git a/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs b/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs
new file mode 100644
index 000000000..e908621bc
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/Source/Utils/LocalizationCSVtoXML.cs
@@ -0,0 +1,246 @@
+#if DEBUG
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace Barotrauma
+{
+ class LocalizationCSVtoXML
+ {
+ private static Regex csvSplit = new Regex("(?:^|,)(\"(?:[^\"])*\"|[^,]*)", RegexOptions.Compiled); // Handling commas inside data fields surrounded by ""
+ private static List conversationClosingIndent = new List();
+ private static char[] separator = new char[1] { ',' };
+
+ private const string conversationsPath = "Content/NPCConversations";
+ private const string infoTextPath = "Content/Texts";
+ private const string xmlHeader = "";
+
+ public static void Convert(string language)
+ {
+ List conversationFiles = new List();
+ List infoTextFiles = new List();
+
+ language = language.CapitaliseFirstInvariant();
+
+ foreach (string filePath in Directory.GetFiles(conversationsPath, "*.csv", SearchOption.AllDirectories))
+ {
+ conversationFiles.Add(filePath);
+ }
+
+ foreach (string filePath in Directory.GetFiles(infoTextPath, "*.csv", SearchOption.AllDirectories))
+ {
+ infoTextFiles.Add(filePath);
+ }
+
+ for (int i = 0; i < conversationFiles.Count; i++)
+ {
+ List xmlContent = ConvertConversationsToXML(File.ReadAllLines(conversationFiles[i], Encoding.UTF8), language);
+ string xmlFileFullPath = $"{conversationsPath}/NPCConversations_{language}_NEW.xml";
+ File.WriteAllLines(xmlFileFullPath, xmlContent);
+ DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath);
+ }
+
+ for (int i = 0; i < infoTextFiles.Count; i++)
+ {
+ List xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[i], Encoding.UTF8), language);
+ string xmlFileFullPath = $"{infoTextPath}/{language}Vanilla_NEW.xml";
+ File.WriteAllLines(xmlFileFullPath, xmlContent);
+ DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath);
+ }
+
+ if (conversationFiles.Count == 0 && infoTextFiles.Count == 0)
+ {
+ DebugConsole.ThrowError("No .csv files found to convert.");
+ }
+ }
+
+ private static List ConvertInfoTextToXML(string[] csvContent, string language)
+ {
+ List xmlContent = new List();
+ xmlContent.Add(xmlHeader);
+
+ xmlContent.Add($"");
+ xmlContent.Add(string.Empty);
+
+ for (int i = 0; i < csvContent.Length; i++)
+ {
+ csvContent[i] = csvContent[i].Trim(separator);
+
+ if (csvContent[i].Length == 0)
+ {
+ xmlContent.Add(string.Empty);
+ }
+ else
+ {
+ string[] split = csvContent[i].Split(separator, 2);
+
+ if (split.Length == 2)
+ {
+ split[1] = split[1].Replace("\"", ""); // Replaces quotation marks around data that are added when exporting via excel
+ xmlContent.Add($"<{split[0]}>{split[1]}{split[0]}>");
+ }
+ else if (split[0].Contains(".")) // An empty field
+ {
+ xmlContent.Add($"<{split[0]}>{split[0]}>");
+ }
+ else // A header
+ {
+ xmlContent.Add($"");
+ }
+ }
+ }
+
+ xmlContent.Add(string.Empty);
+ xmlContent.Add("");
+
+ return xmlContent;
+ }
+
+ private static List ConvertConversationsToXML(string[] csvContent, string language)
+ {
+ List xmlContent = new List();
+ xmlContent.Add(xmlHeader);
+
+ xmlContent.Add($"");
+ xmlContent.Add(string.Empty);
+
+ for (int i = 0; i < csvContent.Length; i++)
+ {
+ string[] split = SplitCSV(csvContent[i]);
+
+ int emptyFields = 0;
+
+ for (int j = 0; j < split.Length; j++)
+ {
+ if (split[j] == string.Empty) emptyFields++;
+ }
+
+ if (emptyFields == split.Length) // Empty line with only commas, indicates the end of the previous conversation
+ {
+ HandleClosingElements(xmlContent, 0);
+ xmlContent.Add(string.Empty);
+ continue;
+ }
+ else if (emptyFields == split.Length - 1 && split[0] != string.Empty) // A header
+ {
+ xmlContent.Add($"");
+ continue;
+ }
+
+ string speaker = split[1];
+ int depthIndex = int.Parse(split[2]);
+ // 3 = original line
+ string line = split[4].Replace("\"", "");
+ string flags = split[5].Replace("\"", "");
+ string allowedJobs = split[6].Replace("\"", "");
+ string speakerTags = split[7].Replace("\"", "");
+ string minIntensity = split[8].Replace("\"", "");
+ string maxIntensity = split[9].Replace("\"", "");
+
+ string element =
+ $"{GetIndenting(depthIndex)}" +
+ $" depthIndex;
+ }
+
+ if (!nextIsSubConvo)
+ {
+ xmlContent.Add(element.TrimEnd() + "/>");
+ if (nextDepth < depthIndex)
+ {
+ HandleClosingElements(xmlContent, nextDepth);
+ }
+ }
+ else
+ {
+ xmlContent.Add(element.TrimEnd() + ">");
+ conversationClosingIndent.Add(depthIndex);
+ }
+ }
+ else
+ {
+ xmlContent.Add(element.TrimEnd() + "/>");
+ }
+ }
+
+ xmlContent.Add(string.Empty);
+ xmlContent.Add("");
+
+ return xmlContent;
+ }
+
+ private static void HandleClosingElements(List xmlContent, int targetDepth)
+ {
+ if (conversationClosingIndent.Count == 0) return;
+
+ for (int k = conversationClosingIndent.Count - 1; k >= 0; k--)
+ {
+ int currentIndent = conversationClosingIndent[k];
+ if (currentIndent < targetDepth) break;
+ xmlContent.Add($"{GetIndenting(currentIndent)}");
+ conversationClosingIndent.RemoveAt(k);
+ }
+ }
+
+ private static string[] SplitCSV(string input) // Splits the .csv with regex, leaving commas inside quotation marks intact
+ {
+ List list = new List();
+ string curr = null;
+ foreach (Match match in csvSplit.Matches(input))
+ {
+ curr = match.Value;
+ if (0 == curr.Length)
+ {
+ list.Add("");
+ }
+
+ list.Add(curr.TrimStart(','));
+ }
+
+ return list.ToArray();
+ }
+
+ private static string GetIndenting(int depthIndex)
+ {
+ string indenting = string.Empty;
+
+ for (int i = 0; i < depthIndex; i++)
+ {
+ indenting += "\t";
+ }
+
+ return indenting;
+ }
+
+ private static string GetVariable(string name, string value)
+ {
+ if (value == string.Empty)
+ {
+ return string.Empty;
+ }
+ else
+ {
+ return $"{name}=\"{value}\" ";
+ }
+ }
+ }
+}
+#endif
diff --git a/Barotrauma/BarotraumaClient/WindowsClient.csproj b/Barotrauma/BarotraumaClient/WindowsClient.csproj
index 0ace19fec..2c4bc8e82 100644
--- a/Barotrauma/BarotraumaClient/WindowsClient.csproj
+++ b/Barotrauma/BarotraumaClient/WindowsClient.csproj
@@ -169,6 +169,12 @@
PreserveNewest
+
+
+
+
+
+
PreserveNewest
@@ -250,6 +256,7 @@
PreserveNewest
+
diff --git a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs
index 3c37eb862..95f9d1e53 100644
--- a/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaServer/Source/Characters/CharacterNetworking.cs
@@ -166,11 +166,8 @@ namespace Barotrauma
//character changed the direction they're facing
c.KickAFKTimer = 0.0f;
}
-
- if (newInput.HasFlag(InputNetFlags.Aim))
- {
- newAim = msg.ReadUInt16();
- }
+
+ newAim = msg.ReadUInt16();
if (newInput.HasFlag(InputNetFlags.Select) ||
newInput.HasFlag(InputNetFlags.Use) ||
newInput.HasFlag(InputNetFlags.Health) ||
@@ -181,12 +178,15 @@ namespace Barotrauma
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
{
+ if ((i > 0 && memInput[i - 1].intAim != newAim))
+ {
+ c.KickAFKTimer = 0.0f;
+ }
NetInputMem newMem = new NetInputMem
{
states = newInput,
intAim = newAim,
interact = newInteract,
-
networkUpdateID = (ushort)(networkUpdateID - i)
};
memInput.Insert(i, newMem);
diff --git a/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs b/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs
index 3dd111358..6d13e1aaf 100644
--- a/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs
+++ b/Barotrauma/BarotraumaServer/Source/Items/Inventory.cs
@@ -62,7 +62,7 @@ namespace Barotrauma
if (newItemIDs[i] == 0 || (newItem != Items[i]))
{
- if (Items[i] != null) Items[i].Drop();
+ if (Items[i] != null) Items[i].Drop(null);
System.Diagnostics.Debug.Assert(Items[i] == null);
}
}
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
index 9eb5a5e2f..666aad960 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServer.cs
@@ -355,8 +355,10 @@ namespace Barotrauma.Networking
entityEventManager.Update(connectedClients);
- foreach (Character character in Character.CharacterList)
+ //go through the characters backwards to give rejoining clients control of the latest created character
+ for (int i = Character.CharacterList.Count - 1; i >= 0; i--)
{
+ Character character = Character.CharacterList[i];
if (character.IsDead || !character.ClientDisconnected) continue;
character.KillDisconnectedTimer += deltaTime;
@@ -1957,8 +1959,8 @@ namespace Barotrauma.Networking
if (client == null) return;
if (client.Connection == OwnerConnection) return;
- string msg = DisconnectReason.Banned.ToString();
- DisconnectClient(client, $"ServerMessage.BannedFromServer~[client]={client.Name}", msg, reason);
+ string targetMsg = DisconnectReason.Banned.ToString();
+ DisconnectClient(client, $"ServerMessage.BannedFromServer~[client]={client.Name}", targetMsg, reason);
if (client.SteamID == 0 || range)
{
@@ -2764,6 +2766,747 @@ namespace Barotrauma.Networking
}
}
+ public override void Disconnect()
+ {
+ serverSettings.BanList.Save();
+ serverSettings.SaveSettings();
+ SteamManager.CloseServer();
+
+ if (registeredToMaster)
+ {
+ if (restClient != null)
+ {
+ var request = new RestRequest("masterserver2.php", Method.GET);
+ request.AddParameter("action", "removeserver");
+ request.AddParameter("serverport", Port);
+ restClient.Execute(request);
+ restClient = null;
+ }
+ }
+
+ if (serverSettings.SaveServerLogs)
+ {
+ Log("Shutting down the server...", ServerLog.MessageType.ServerMessage);
+ serverSettings.ServerLog.Save();
+ }
+
+ GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown");
+ server.Shutdown(DisconnectReason.ServerShutdown.ToString());
+ }
+
+ public void SendConsoleMessage(string txt, Client recipient)
+ {
+ ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Console, null);
+ SendDirectChatMessage(msg, recipient);
+ }
+
+ public void SendDirectChatMessage(ChatMessage msg, Client recipient)
+ {
+ if (recipient == null)
+ {
+ string errorMsg = "Attempted to send a chat message to a null client.\n" + Environment.StackTrace;
+ DebugConsole.ThrowError(errorMsg);
+ GameAnalyticsManager.AddErrorEventOnce("GameServer.SendDirectChatMessage:ClientNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
+ return;
+ }
+
+ msg.NetStateID = recipient.ChatMsgQueue.Count > 0 ?
+ (ushort)(recipient.ChatMsgQueue.Last().NetStateID + 1) :
+ (ushort)(recipient.LastRecvChatMsgID + 1);
+
+ recipient.ChatMsgQueue.Add(msg);
+ recipient.LastChatMsgQueueID = msg.NetStateID;
+ }
+
+ ///
+ /// Add the message to the chatbox and pass it to all clients who can receive it
+ ///
+ public void SendChatMessage(string message, ChatMessageType? type = null, Client senderClient = null, Character senderCharacter = null)
+ {
+ string senderName = "";
+
+ Client targetClient = null;
+
+ if (type == null)
+ {
+ string command = ChatMessage.GetChatMessageCommand(message, out string tempStr);
+ switch (command.ToLowerInvariant())
+ {
+ case "r":
+ case "radio":
+ type = ChatMessageType.Radio;
+ break;
+ case "d":
+ case "dead":
+ type = ChatMessageType.Dead;
+ break;
+ default:
+ if (command != "")
+ {
+ if (command == name.ToLowerInvariant())
+ {
+ //a private message to the host
+ }
+ else
+ {
+ targetClient = connectedClients.Find(c =>
+ command == c.Name.ToLowerInvariant() ||
+ (c.Character != null && command == c.Character.Name.ToLowerInvariant()));
+
+ if (targetClient == null)
+ {
+ if (senderClient != null)
+ {
+ var chatMsg = ChatMessage.Create(
+ "", $"ServerMessage.PlayerNotFound~[player]={command}",
+ ChatMessageType.Error, null);
+
+ chatMsg.NetStateID = senderClient.ChatMsgQueue.Count > 0 ?
+ (ushort)(senderClient.ChatMsgQueue.Last().NetStateID + 1) :
+ (ushort)(senderClient.LastRecvChatMsgID + 1);
+
+ senderClient.ChatMsgQueue.Add(chatMsg);
+ senderClient.LastChatMsgQueueID = chatMsg.NetStateID;
+ }
+ else
+ {
+ AddChatMessage($"ServerMessage.PlayerNotFound~[player]={command}", ChatMessageType.Error);
+ }
+
+ return;
+ }
+ }
+
+ type = ChatMessageType.Private;
+ }
+ else
+ {
+ type = ChatMessageType.Default;
+ }
+ break;
+ }
+
+ message = tempStr;
+ }
+
+ if (gameStarted)
+ {
+ if (senderClient == null)
+ {
+ //msg sent by the server
+ if (senderCharacter == null)
+ {
+ senderName = name;
+ }
+ else //msg sent by an AI character
+ {
+ senderName = senderCharacter.Name;
+ }
+ }
+ else //msg sent by a client
+ {
+ senderCharacter = senderClient.Character;
+ senderName = senderCharacter == null ? senderClient.Name : senderCharacter.Name;
+
+ //sender doesn't have a character or the character can't speak -> only ChatMessageType.Dead allowed
+ if (senderCharacter == null || senderCharacter.IsDead || senderCharacter.SpeechImpediment >= 100.0f)
+ {
+ type = ChatMessageType.Dead;
+ }
+ else if (type == ChatMessageType.Private)
+ {
+ //sender has an alive character, sending private messages not allowed
+ return;
+ }
+
+ }
+ }
+ else
+ {
+ if (senderClient == null)
+ {
+ //msg sent by the server
+ if (senderCharacter == null)
+ {
+ senderName = name;
+ }
+ else //sent by an AI character, not allowed when the game is not running
+ {
+ return;
+ }
+ }
+ else //msg sent by a client
+ {
+ //game not started -> clients can only send normal and private chatmessages
+ if (type != ChatMessageType.Private) type = ChatMessageType.Default;
+ senderName = senderClient.Name;
+ }
+ }
+
+ //check if the client is allowed to send the message
+ WifiComponent senderRadio = null;
+ switch (type)
+ {
+ case ChatMessageType.Radio:
+ case ChatMessageType.Order:
+ if (senderCharacter == null) return;
+
+ //return if senderCharacter doesn't have a working radio
+ var radio = senderCharacter.Inventory?.Items.FirstOrDefault(i => i != null && i.GetComponent() != null);
+ if (radio == null || !senderCharacter.HasEquippedItem(radio)) return;
+
+ senderRadio = radio.GetComponent();
+ if (!senderRadio.CanTransmit()) return;
+ break;
+ case ChatMessageType.Dead:
+ //character still alive and capable of speaking -> dead chat not allowed
+ if (senderClient != null && senderCharacter != null && !senderCharacter.IsDead && senderCharacter.SpeechImpediment < 100.0f)
+ {
+ return;
+ }
+ break;
+ }
+
+ if (type == ChatMessageType.Server)
+ {
+ senderName = null;
+ senderCharacter = null;
+ }
+ else if (type == ChatMessageType.Radio)
+ {
+ //send to chat-linked wifi components
+ senderRadio.TransmitSignal(0, message, senderRadio.Item, senderCharacter, false);
+ }
+
+ //check which clients can receive the message and apply distance effects
+ foreach (Client client in ConnectedClients)
+ {
+ string modifiedMessage = message;
+
+ switch (type)
+ {
+ case ChatMessageType.Default:
+ case ChatMessageType.Radio:
+ case ChatMessageType.Order:
+ if (senderCharacter != null &&
+ client.Character != null && !client.Character.IsDead)
+ {
+ modifiedMessage = ChatMessage.ApplyDistanceEffect(message, (ChatMessageType)type, senderCharacter, client.Character);
+
+ //too far to hear the msg -> don't send
+ if (string.IsNullOrWhiteSpace(modifiedMessage)) continue;
+ }
+ break;
+ case ChatMessageType.Dead:
+ //character still alive -> don't send
+ if (client != senderClient && client.Character != null && !client.Character.IsDead) continue;
+ break;
+ case ChatMessageType.Private:
+ //private msg sent to someone else than this client -> don't send
+ if (client != targetClient && client != senderClient) continue;
+ break;
+ }
+
+ var chatMsg = ChatMessage.Create(
+ senderName,
+ modifiedMessage,
+ (ChatMessageType)type,
+ senderCharacter);
+
+ SendDirectChatMessage(chatMsg, client);
+ }
+
+ if (type.Value != ChatMessageType.MessageBox)
+ {
+ string myReceivedMessage = type == ChatMessageType.Server || type == ChatMessageType.Error ? TextManager.GetServerMessage(message) : message;
+ if (!string.IsNullOrWhiteSpace(myReceivedMessage) &&
+ (targetClient == null || senderClient == null))
+ {
+ AddChatMessage(myReceivedMessage, (ChatMessageType)type, senderName, senderCharacter);
+ }
+ }
+ }
+
+ public void SendOrderChatMessage(OrderChatMessage message)
+ {
+ if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) return;
+ ChatMessageType messageType = ChatMessage.CanUseRadio(message.Sender) ? ChatMessageType.Radio : ChatMessageType.Default;
+
+ //check which clients can receive the message and apply distance effects
+ foreach (Client client in ConnectedClients)
+ {
+ string modifiedMessage = message.Text;
+
+ if (message.Sender != null &&
+ client.Character != null && !client.Character.IsDead)
+ {
+ modifiedMessage = ChatMessage.ApplyDistanceEffect(message.Text, messageType, message.Sender, client.Character);
+
+ //too far to hear the msg -> don't send
+ if (string.IsNullOrWhiteSpace(modifiedMessage)) continue;
+ }
+
+ SendDirectChatMessage(message, client);
+ }
+
+ string myReceivedMessage = message.Text;
+
+ if (!string.IsNullOrWhiteSpace(myReceivedMessage))
+ {
+ AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, myReceivedMessage, message.TargetEntity, message.TargetCharacter, message.Sender));
+ }
+ }
+
+ private void FileTransferChanged(FileSender.FileTransferOut transfer)
+ {
+ Client recipient = connectedClients.Find(c => c.Connection == transfer.Connection);
+ if (transfer.FileType == FileTransferType.CampaignSave &&
+ (transfer.Status == FileTransferStatus.Sending || transfer.Status == FileTransferStatus.Finished) &&
+ recipient.LastCampaignSaveSendTime != null)
+ {
+ recipient.LastCampaignSaveSendTime.Second = (float)NetTime.Now;
+ }
+ }
+
+ public void SendCancelTransferMsg(FileSender.FileTransferOut transfer)
+ {
+ NetOutgoingMessage msg = server.CreateMessage();
+ msg.Write((byte)ServerPacketHeader.FILE_TRANSFER);
+ msg.Write((byte)FileTransferMessageType.Cancel);
+ msg.Write((byte)transfer.SequenceChannel);
+ CompressOutgoingMessage(msg);
+ server.SendMessage(msg, transfer.Connection, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
+ }
+
+ public void UpdateVoteStatus()
+ {
+ if (server.Connections.Count == 0 || connectedClients.Count == 0) return;
+
+ Client.UpdateKickVotes(connectedClients);
+
+ var clientsToKick = connectedClients.FindAll(c =>
+ c.Connection != OwnerConnection &&
+ c.KickVoteCount >= connectedClients.Count * serverSettings.KickVoteRequiredRatio);
+ foreach (Client c in clientsToKick)
+ {
+ SendChatMessage($"ServerMessage.KickedFromServer~[client]={c.Name}", ChatMessageType.Server, null);
+ KickClient(c, "ServerMessage.KickedByVote");
+ BanClient(c, "ServerMessage.KickedByVoteAutoBan", duration: TimeSpan.FromSeconds(serverSettings.AutoBanTime));
+ }
+
+ GameMain.NetLobbyScreen.LastUpdateID++;
+
+ SendVoteStatus(connectedClients);
+
+ if (serverSettings.Voting.AllowEndVoting && EndVoteMax > 0 &&
+ ((float)EndVoteCount / (float)EndVoteMax) >= serverSettings.EndVoteRequiredRatio)
+ {
+ Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", ServerLog.MessageType.ServerMessage);
+ EndGame();
+ }
+ }
+
+ public void SendVoteStatus(List recipients)
+ {
+ NetOutgoingMessage msg = server.CreateMessage();
+ msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
+ msg.Write((byte)ServerNetObject.VOTE);
+ serverSettings.Voting.ServerWrite(msg);
+ msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
+
+ CompressOutgoingMessage(msg);
+
+ server.SendMessage(msg, recipients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
+ }
+
+ public void UpdateClientPermissions(Client client)
+ {
+ if (client.SteamID > 0)
+ {
+ serverSettings.ClientPermissions.RemoveAll(cp => cp.SteamID == client.SteamID);
+ if (client.Permissions != ClientPermissions.None)
+ {
+ serverSettings.ClientPermissions.Add(new ServerSettings.SavedClientPermission(
+ client.Name,
+ client.SteamID,
+ client.Permissions,
+ client.PermittedConsoleCommands));
+ }
+ }
+ else
+ {
+ serverSettings.ClientPermissions.RemoveAll(cp => client.IPMatches(cp.IP));
+ if (client.Permissions != ClientPermissions.None)
+ {
+ serverSettings.ClientPermissions.Add(new ServerSettings.SavedClientPermission(
+ client.Name,
+ client.Connection.RemoteEndPoint.Address,
+ client.Permissions,
+ client.PermittedConsoleCommands));
+ }
+ }
+
+ var msg = server.CreateMessage();
+ msg.Write((byte)ServerPacketHeader.PERMISSIONS);
+ client.WritePermissions(msg);
+ CompressOutgoingMessage(msg);
+
+ //send the message to the client whose permissions are being modified and the clients who are allowed to modify permissions
+ List recipients = new List() { client.Connection };
+ foreach (Client otherClient in connectedClients)
+ {
+ if (otherClient.HasPermission(ClientPermissions.ManagePermissions) && !recipients.Contains(otherClient.Connection))
+ {
+ recipients.Add(otherClient.Connection);
+ }
+ }
+ server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
+
+ serverSettings.SaveClientPermissions();
+ }
+
+ public void GiveAchievement(Character character, string achievementIdentifier)
+ {
+ achievementIdentifier = achievementIdentifier.ToLowerInvariant();
+ foreach (Client client in connectedClients)
+ {
+ if (client.Character == character)
+ {
+ GiveAchievement(client, achievementIdentifier);
+ return;
+ }
+ }
+ }
+
+ public void GiveAchievement(Client client, string achievementIdentifier)
+ {
+ if (client.GivenAchievements.Contains(achievementIdentifier)) return;
+ client.GivenAchievements.Add(achievementIdentifier);
+
+ var msg = server.CreateMessage();
+ msg.Write((byte)ServerPacketHeader.ACHIEVEMENT);
+ msg.Write(achievementIdentifier);
+
+ CompressOutgoingMessage(msg);
+
+ server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered);
+ }
+
+ public void UpdateCheatsEnabled()
+ {
+ var msg = server.CreateMessage();
+ msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED);
+ msg.Write(DebugConsole.CheatsEnabled);
+ msg.WritePadBits();
+
+ CompressOutgoingMessage(msg);
+
+ server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
+ }
+
+ public void SetClientCharacter(Client client, Character newCharacter)
+ {
+ if (client == null) return;
+
+ //the client's previous character is no longer a remote player
+ if (client.Character != null)
+ {
+ client.Character.IsRemotePlayer = false;
+ client.Character.OwnerClientIP = null;
+ client.Character.OwnerClientName = null;
+ }
+
+ if (newCharacter == null)
+ {
+ if (client.Character != null) //removing control of the current character
+ {
+ CreateEntityEvent(client.Character, new object[] { NetEntityEvent.Type.Control, null });
+ client.Character = null;
+ }
+ }
+ else //taking control of a new character
+ {
+ newCharacter.ClientDisconnected = false;
+ newCharacter.KillDisconnectedTimer = 0.0f;
+ newCharacter.ResetNetState();
+ if (client.Character != null)
+ {
+ newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID;
+ }
+
+ newCharacter.OwnerClientIP = client.Connection.RemoteEndPoint.Address.ToString();
+ newCharacter.OwnerClientName = client.Name;
+ newCharacter.IsRemotePlayer = true;
+ newCharacter.Enabled = true;
+ client.Character = newCharacter;
+ CreateEntityEvent(newCharacter, new object[] { NetEntityEvent.Type.Control, client });
+ }
+ }
+
+ private void UpdateCharacterInfo(NetIncomingMessage message, Client sender)
+ {
+ sender.SpectateOnly = message.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection);
+ if (sender.SpectateOnly)
+ {
+ return;
+ }
+
+ Gender gender = Gender.Male;
+ Race race = Race.White;
+ int headSpriteId = 0;
+ try
+ {
+ gender = (Gender)message.ReadByte();
+ race = (Race)message.ReadByte();
+ headSpriteId = message.ReadByte();
+ }
+ catch (Exception e)
+ {
+ //gender = Gender.Male;
+ //race = Race.White;
+ //headSpriteId = 0;
+ DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }");
+ }
+ int hairIndex = message.ReadByte();
+ int beardIndex = message.ReadByte();
+ int moustacheIndex = message.ReadByte();
+ int faceAttachmentIndex = message.ReadByte();
+
+ List jobPreferences = new List();
+ int count = message.ReadByte();
+ for (int i = 0; i < Math.Min(count, 3); i++)
+ {
+ string jobIdentifier = message.ReadString();
+
+ JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Identifier == jobIdentifier);
+ if (jobPrefab != null) jobPreferences.Add(jobPrefab);
+ }
+
+ sender.CharacterInfo = new CharacterInfo(Character.HumanConfigFile, sender.Name);
+ sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
+
+ //if the client didn't provide job preferences, we'll use the preferences that are randomly assigned in the Client constructor
+ Debug.Assert(sender.JobPreferences.Count > 0);
+ if (jobPreferences.Count > 0)
+ {
+ sender.JobPreferences = jobPreferences;
+ }
+ }
+
+ public void AssignJobs(List unassigned)
+ {
+ unassigned = new List(unassigned);
+
+ Dictionary assignedClientCount = new Dictionary();
+ foreach (JobPrefab jp in JobPrefab.List)
+ {
+ assignedClientCount.Add(jp, 0);
+ }
+
+ Character.TeamType teamID = Character.TeamType.None;
+ if (unassigned.Count > 0) { teamID = unassigned[0].TeamID; }
+
+ //if we're playing a multiplayer campaign, check which clients already have a character and a job
+ //(characters are persistent in campaigns)
+ if (GameMain.GameSession.GameMode is MultiPlayerCampaign multiplayerCampaign)
+ {
+ var campaignAssigned = multiplayerCampaign.GetAssignedJobs(connectedClients);
+ //remove already assigned clients from unassigned
+ unassigned.RemoveAll(u => campaignAssigned.ContainsKey(u));
+ //add up to assigned client count
+ foreach (KeyValuePair clientJob in campaignAssigned)
+ {
+ assignedClientCount[clientJob.Value.Prefab]++;
+ clientJob.Key.AssignedJob = clientJob.Value.Prefab;
+ }
+ }
+
+ //count the clients who already have characters with an assigned job
+ foreach (Client c in connectedClients)
+ {
+ if (c.TeamID != teamID || unassigned.Contains(c)) continue;
+ if (c.Character?.Info?.Job != null && !c.Character.IsDead)
+ {
+ assignedClientCount[c.Character.Info.Job.Prefab]++;
+ }
+ }
+
+ //if any of the players has chosen a job that is Always Allowed, give them that job
+ for (int i = unassigned.Count - 1; i >= 0; i--)
+ {
+ if (unassigned[i].JobPreferences.Count == 0) continue;
+ if (!unassigned[i].JobPreferences[0].AllowAlways) continue;
+ unassigned[i].AssignedJob = unassigned[i].JobPreferences[0];
+ unassigned.RemoveAt(i);
+ }
+
+ //go throught the jobs whose MinNumber>0 (i.e. at least one crew member has to have the job)
+ bool unassignedJobsFound = true;
+ while (unassignedJobsFound && unassigned.Count > 0)
+ {
+ unassignedJobsFound = false;
+
+ foreach (JobPrefab jobPrefab in JobPrefab.List)
+ {
+ if (unassigned.Count == 0) break;
+ if (jobPrefab.MinNumber < 1 || assignedClientCount[jobPrefab] >= jobPrefab.MinNumber) continue;
+
+ //find the client that wants the job the most, or force it to random client if none of them want it
+ Client assignedClient = FindClientWithJobPreference(unassigned, jobPrefab, true);
+
+ assignedClient.AssignedJob = jobPrefab;
+ assignedClientCount[jobPrefab]++;
+ unassigned.Remove(assignedClient);
+
+ //the job still needs more crew members, set unassignedJobsFound to true to keep the while loop running
+ if (assignedClientCount[jobPrefab] < jobPrefab.MinNumber) unassignedJobsFound = true;
+ }
+ }
+
+ //attempt to give the clients a job they have in their job preferences
+ for (int i = unassigned.Count - 1; i >= 0; i--)
+ {
+ foreach (JobPrefab preferredJob in unassigned[i].JobPreferences)
+ {
+ //the maximum number of players that can have this job hasn't been reached yet
+ // -> assign it to the client
+ if (assignedClientCount[preferredJob] < preferredJob.MaxNumber && unassigned[i].Karma >= preferredJob.MinKarma)
+ {
+ unassigned[i].AssignedJob = preferredJob;
+ assignedClientCount[preferredJob]++;
+ unassigned.RemoveAt(i);
+ break;
+ }
+ }
+ }
+
+ //give random jobs to rest of the clients
+ foreach (Client c in unassigned)
+ {
+ //find all jobs that are still available
+ var remainingJobs = JobPrefab.List.FindAll(jp => assignedClientCount[jp] < jp.MaxNumber && c.Karma >= jp.MinKarma);
+
+ //all jobs taken, give a random job
+ if (remainingJobs.Count == 0)
+ {
+ DebugConsole.ThrowError("Failed to assign a suitable job for \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
+ int jobIndex = Rand.Range(0, JobPrefab.List.Count);
+ int skips = 0;
+ while (c.Karma < JobPrefab.List[jobIndex].MinKarma)
+ {
+ jobIndex++;
+ skips++;
+ if (jobIndex >= JobPrefab.List.Count) jobIndex -= JobPrefab.List.Count;
+ if (skips >= JobPrefab.List.Count) break;
+ }
+ c.AssignedJob = JobPrefab.List[jobIndex];
+ assignedClientCount[c.AssignedJob]++;
+ }
+ else //some jobs still left, choose one of them by random
+ {
+ c.AssignedJob = remainingJobs[Rand.Range(0, remainingJobs.Count)];
+ assignedClientCount[c.AssignedJob]++;
+ }
+ }
+ }
+
+ public void AssignBotJobs(List bots, Character.TeamType teamID)
+ {
+ Dictionary assignedPlayerCount = new Dictionary();
+ foreach (JobPrefab jp in JobPrefab.List)
+ {
+ assignedPlayerCount.Add(jp, 0);
+ }
+
+ //count the clients who already have characters with an assigned job
+ foreach (Client c in connectedClients)
+ {
+ if (c.TeamID != teamID) continue;
+ if (c.Character?.Info?.Job != null && !c.Character.IsDead)
+ {
+ assignedPlayerCount[c.Character.Info.Job.Prefab]++;
+ }
+ else if (c.CharacterInfo?.Job != null)
+ {
+ assignedPlayerCount[c.CharacterInfo?.Job.Prefab]++;
+ }
+ }
+
+ List unassignedBots = new List(bots);
+ foreach (CharacterInfo bot in bots)
+ {
+ foreach (JobPrefab jobPrefab in JobPrefab.List)
+ {
+ if (jobPrefab.MinNumber < 1 || assignedPlayerCount[jobPrefab] >= jobPrefab.MinNumber) continue;
+ bot.Job = new Job(jobPrefab);
+ assignedPlayerCount[jobPrefab]++;
+ unassignedBots.Remove(bot);
+ break;
+ }
+ }
+
+ //find a suitable job for the rest of the players
+ foreach (CharacterInfo c in unassignedBots)
+ {
+ //find all jobs that are still available
+ var remainingJobs = JobPrefab.List.FindAll(jp => assignedPlayerCount[jp] < jp.MaxNumber);
+ //all jobs taken, give a random job
+ if (remainingJobs.Count == 0)
+ {
+ DebugConsole.ThrowError("Failed to assign a suitable job for bot \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
+ c.Job = new Job(JobPrefab.List[Rand.Range(0, JobPrefab.List.Count)]);
+ assignedPlayerCount[c.Job.Prefab]++;
+ }
+ else //some jobs still left, choose one of them by random
+ {
+ c.Job = new Job(remainingJobs[Rand.Range(0, remainingJobs.Count)]);
+ assignedPlayerCount[c.Job.Prefab]++;
+ }
+ }
+ }
+
+ private Client FindClientWithJobPreference(List clients, JobPrefab job, bool forceAssign = false)
+ {
+ int bestPreference = 0;
+ Client preferredClient = null;
+ foreach (Client c in clients)
+ {
+ if (c.Karma < job.MinKarma) continue;
+ int index = c.JobPreferences.IndexOf(job);
+ if (index == -1) index = 1000;
+
+ if (preferredClient == null || index < bestPreference)
+ {
+ bestPreference = index;
+ preferredClient = c;
+ }
+ }
+
+ //none of the clients wants the job, assign it to random client
+ if (forceAssign && preferredClient == null)
+ {
+ preferredClient = clients[Rand.Int(clients.Count)];
+ }
+
+ return preferredClient;
+ }
+
+ public static void Log(string line, ServerLog.MessageType messageType)
+ {
+ if (GameMain.Server == null || !GameMain.Server.ServerSettings.SaveServerLogs) return;
+
+ GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType);
+
+ foreach (Client client in GameMain.Server.ConnectedClients)
+ {
+ if (!client.HasPermission(ClientPermissions.ServerLog)) continue;
+ //use sendername as the message type
+ GameMain.Server.SendDirectChatMessage(
+ ChatMessage.Create(messageType.ToString(), line, ChatMessageType.ServerLog, null),
+ client);
+ }
+ }
+
public override void Disconnect()
{
serverSettings.BanList.Save();
diff --git a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs
index e657f8a7f..fe0b66db2 100644
--- a/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs
+++ b/Barotrauma/BarotraumaServer/Source/Networking/GameServerLogin.cs
@@ -140,7 +140,7 @@ namespace Barotrauma.Networking
if (GameMain.Config.RequireSteamAuthentication)
{
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed, (" + status + ").", ServerLog.MessageType.ServerMessage);
- unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString() + "; (" + status.ToString() + ")");
+ unauthClient.Connection.Disconnect(DisconnectReason.SteamAuthenticationFailed.ToString() + "/ (" + status.ToString() + ")");
}
else
{
@@ -500,7 +500,7 @@ namespace Barotrauma.Networking
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message)
{
- inc.SenderConnection.Disconnect(reason.ToString() + "; " + message);
+ inc.SenderConnection.Disconnect(reason.ToString() + "/ " + message);
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
if (unauthClient != null)
{
diff --git a/Barotrauma/BarotraumaShared/SharedContent.projitems b/Barotrauma/BarotraumaShared/SharedContent.projitems
index d5ac9408f..bec8045ae 100644
--- a/Barotrauma/BarotraumaShared/SharedContent.projitems
+++ b/Barotrauma/BarotraumaShared/SharedContent.projitems
@@ -1029,25 +1029,34 @@
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
PreserveNewest
-
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
PreserveNewest
@@ -1298,15 +1307,6 @@
PreserveNewest
-
- PreserveNewest
-
-
- PreserveNewest
-
-
- PreserveNewest
-
PreserveNewest
@@ -3108,5 +3108,8 @@
PreserveNewest
+
+ PreserveNewest
+
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs
index 73c7d98f4..898af1d9a 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/HumanAIController.cs
@@ -22,7 +22,6 @@ namespace Barotrauma
public const float HULL_SAFETY_THRESHOLD = 50;
- // TODO: update the list when someone gives a report
public HashSet UnsafeHulls { get; private set; } = new HashSet();
private SteeringManager outsideSteering, insideSteering;
@@ -136,10 +135,11 @@ namespace Barotrauma
Character.AnimController.IgnorePlatforms = ignorePlatforms;
- if (Character.IsClimbing)
- {
- Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
- }
+ // Suspect that this causes issues when trying to exit from the ladders -> could try to check if the next node is ladder?
+ //if (Character.IsClimbing)
+ //{
+ // Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
+ //}
Vector2 targetMovement = AnimController.TargetMovement;
@@ -187,7 +187,7 @@ namespace Barotrauma
// Try to put the mask in an Any slot, and drop it if that fails
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List() { InvSlotType.Any }))
{
- mask.Drop();
+ mask.Drop(Character);
}
}
}
@@ -198,7 +198,7 @@ namespace Barotrauma
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
{
// TODO: take the item where it was taken from?
- extinguisherItem.Drop();
+ extinguisherItem.Drop(Character);
}
}
@@ -227,6 +227,10 @@ namespace Barotrauma
PropagateHullSafety(Character, Character.CurrentHull);
}
}
+
+ private void ReportProblems()
+ {
+ if (GameMain.Client != null) return;
protected void ReportProblems()
{
@@ -271,10 +275,6 @@ namespace Barotrauma
}
}
}
-
- private void ReportProblems()
- {
- if (GameMain.Client != null) return;
private void UpdateSpeaking()
{
@@ -293,41 +293,68 @@ namespace Barotrauma
Character.Speak(TextManager.Get("DialogPressure").Replace("[roomname]", Character.CurrentHull.RoomName), null, 0, "pressure", 30.0f);
}
}
-
+
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
- float totalDamage = attackResult.Damage;
- if (totalDamage <= 0.0f || attacker == null) return;
-
- if (attacker.SpeciesName == Character.SpeciesName)
+ float damage = attackResult.Damage;
+ if (damage < 0) { return; }
+ if (attacker == null || attacker.IsDead || attacker.Removed)
+ {
+ objectiveManager.GetObjective().Priority = 100;
+ return;
+ }
+ if (IsFriendly(attacker))
{
- if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
- {
- // Don't react to damage done by friendly ai, because we know that it's accidental
- return;
- }
if (attacker.AnimController.Anim == Barotrauma.AnimController.Animation.CPR && attacker.SelectedCharacter == Character)
{
// Don't attack characters that damage you while doing cpr, because let's assume that they are helping you.
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
return;
}
+ if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
+ {
+ // Don't react to damage done by friendly ai, because we know that it's accidental
+ objectiveManager.GetObjective().Priority = 100;
+ return;
+ }
float currentVitality = Character.CharacterHealth.Vitality;
- float dmgPercentage = totalDamage / currentVitality * 100;
+ float dmgPercentage = damage / currentVitality * 100;
if (dmgPercentage < currentVitality / 10)
{
// Don't react to a minor amount of (accidental) dmg done by friendly characters
+ objectiveManager.GetObjective().Priority = 100;
return;
}
+ if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
+ {
+ if (combatObjective.Enemy != attacker)
+ {
+ // Replace the old objective with the new.
+ ObjectiveManager.Objectives.Remove(combatObjective);
+ objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
+ }
+ }
+ else
+ {
+ objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1f, Rand.RandSync.Unsynced));
+ }
}
-
- objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () =>
+ else
{
- //the objective in the manager is not necessarily the same as the one we just instantiated,
- //because the objective isn't added if there's already an identical objective in the manager
- var combatObjective = objectiveManager.GetObjective();
- combatObjective.MaxEnemyDamage = Math.Max(totalDamage, combatObjective.MaxEnemyDamage);
- });
+ if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
+ {
+ if (combatObjective.Enemy != attacker)
+ {
+ // Replace the old objective with the new.
+ ObjectiveManager.Objectives.Remove(combatObjective);
+ objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
+ }
+ }
+ else
+ {
+ objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker));
+ }
+ }
}
public void SetOrder(Order order, string option, Character orderGiver, bool speak = true)
@@ -420,7 +447,8 @@ namespace Barotrauma
bool ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires;
bool ignoreWater = HasDivingSuit(Character);
bool ignoreOxygen = ignoreWater || HasDivingGear(Character);
- return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies: false);
+ bool ignoreEnemies = ObjectiveManager.CurrentObjective is AIObjectiveCombat || ObjectiveManager.CurrentOrder is AIObjectiveCombat;
+ return GetHullSafety(hull, Character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
}
public static float GetHullSafety(Hull hull, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
@@ -443,5 +471,8 @@ namespace Barotrauma
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
return MathHelper.Clamp(safety * 100, 0, 100);
}
+
+ // TODO: If the aliens are quaranteed to be in another team than the player, we wouldn't need to check the species.
+ public bool IsFriendly(Character other) => other.TeamID == Character.TeamID && other.SpeciesName == Character.SpeciesName;
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs
index eb5aa57bd..a4ae023a2 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjective.cs
@@ -15,7 +15,7 @@ namespace Barotrauma
public virtual bool KeepDivingGearOn => false;
protected readonly List subObjectives = new List();
- protected float priority;
+ public float Priority { get; set; }
protected readonly Character character;
protected string option;
protected bool abandon;
@@ -26,6 +26,7 @@ namespace Barotrauma
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
+ protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
public string Option
{
@@ -103,20 +104,20 @@ namespace Barotrauma
CurrentSubObjective.SortSubObjectives(objectiveManager);
}
- public virtual float GetPriority(AIObjectiveManager objectiveManager) => priority;
+ public virtual float GetPriority(AIObjectiveManager objectiveManager) => Priority;
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
{
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
if (objectiveManager.CurrentOrder == this)
{
- priority = AIObjectiveManager.OrderPriority;
+ Priority = AIObjectiveManager.OrderPriority;
}
else if (objectiveManager.CurrentObjective == this || subObjective == this)
{
- priority += Devotion * deltaTime;
+ Priority += Devotion * deltaTime;
}
- priority = MathHelper.Clamp(priority, 0, 100);
+ Priority = MathHelper.Clamp(Priority, 0, 100);
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs
index 0463c7ef2..1f8813858 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveCombat.cs
@@ -1,194 +1,268 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
-using System;
using System.Collections.Generic;
using System.Linq;
+using Barotrauma.Extensions;
namespace Barotrauma
{
class AIObjectiveCombat : AIObjective
{
public override string DebugTag => "combat";
- public override bool ForceRun => true;
public override bool KeepDivingGearOn => true;
const float CoolDown = 10.0f;
- //the largest amount of damage the enemy has inflicted on this character
- //(may be higher than enemyStrength if the enemy is e.g. a human using items)
- public float MaxEnemyDamage;
-
- private Character enemy;
-
- private AIObjectiveFindSafety escapeObjective;
-
+ public Character Enemy { get; private set; }
+
+ private Item _weapon;
+ private Item Weapon
+ {
+ get { return _weapon; }
+ set
+ {
+ _weapon = value;
+ _weaponComponent = null;
+ reloadWeaponObjective = null;
+ }
+ }
+ private ItemComponent _weaponComponent;
+ private ItemComponent WeaponComponent
+ {
+ get
+ {
+ if (_weaponComponent == null)
+ {
+ _weaponComponent =
+ Weapon.GetComponent() as ItemComponent ??
+ Weapon.GetComponent() as ItemComponent ??
+ Weapon.GetComponent() as ItemComponent;
+ }
+ return _weaponComponent;
+ }
+ }
private AIObjectiveContainItem reloadWeaponObjective;
+ private Hull retreatTarget;
+ private AIObjectiveGoTo retreatObjective;
private float coolDownTimer;
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
{
- this.enemy = enemy;
+ Enemy = enemy;
coolDownTimer = CoolDown;
+ HumanAIController.ObjectiveManager.GetObjective().Priority = 0;
}
protected override void Act(float deltaTime)
{
coolDownTimer -= deltaTime;
-
- var weapon = character.Inventory.FindItemByTag("weapon");
- if (weapon == null)
+ if (Weapon != null && character.Inventory.Items.Contains(_weapon))
+ {
+ Weapon = null;
+ }
+ if (Weapon == null)
+ {
+ Weapon = GetWeapon();
+ }
+ if (Weapon == null)
{
Escape(deltaTime);
}
- else
+ else if (Equip(deltaTime))
{
- if (!character.SelectedItems.Contains(weapon))
+ if (Reload(deltaTime))
{
- if (character.Inventory.TryPutItem(weapon, 3, true, false, character))
- {
- weapon.Equip(character);
- }
- else
- {
- //couldn't equip the item, escape
- Escape(deltaTime);
- return;
- }
+ Attack(deltaTime);
}
+ }
+ if (!abandon)
+ {
+ Move(deltaTime);
+ }
+ }
- //make sure the weapon is loaded
- var weaponComponent =
- weapon.GetComponent() as ItemComponent ??
- weapon.GetComponent() as ItemComponent ??
- weapon.GetComponent() as ItemComponent;
- if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
+ private Item GetWeapon()
+ {
+ _weaponComponent = null;
+ var weapon = character.Inventory.FindItemByTag("weapon");
+ if (weapon == null)
+ {
+ foreach (var item in character.Inventory.Items)
{
- var containedItems = weapon.ContainedItems;
- foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
+ if (item == null) { continue; }
+ foreach (var component in item.Components)
{
- Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
- if (containedItem == null)
+ if (component is MeleeWeapon || component is RangedWeapon)
{
- var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent());
- if (!newReloadWeaponObjective.IsDuplicate(reloadWeaponObjective))
+ return item;
+ }
+ var effects = component.statusEffectLists;
+ if (effects != null)
+ {
+ foreach (var statusEffects in effects.Values)
{
- reloadWeaponObjective = newReloadWeaponObjective;
+ foreach (var statusEffect in statusEffects)
+ {
+ if (statusEffect.Afflictions.Any())
+ {
+ return item;
+ }
+ }
}
}
}
}
+ }
+ return weapon;
+ }
- if (reloadWeaponObjective != null)
+ private bool Equip(float deltaTime)
+ {
+ if (!character.SelectedItems.Contains(Weapon))
+ {
+ if (character.Inventory.TryPutItem(Weapon, 3, true, false, character))
{
- if (reloadWeaponObjective.IsCompleted())
- {
- reloadWeaponObjective = null;
- }
- else if (!reloadWeaponObjective.CanBeCompleted)
- {
- Escape(deltaTime);
- }
- else
- {
- reloadWeaponObjective.TryComplete(deltaTime);
- }
- return;
+ Weapon.Equip(character);
}
-
- character.CursorPosition = enemy.Position;
- character.SetInput(InputType.Aim, false, true);
-
- Vector2 enemyDiff = Vector2.Normalize(enemy.Position - character.Position);
- if (!MathUtils.IsValid(enemyDiff)) enemyDiff = Rand.Vector(1.0f);
- float weaponAngle = ((weapon.body.Dir == 1.0f) ? weapon.body.Rotation : weapon.body.Rotation - MathHelper.Pi);
- Vector2 weaponDir = new Vector2((float)Math.Cos(weaponAngle), (float)Math.Sin(weaponAngle));
-
- if (Vector2.Dot(enemyDiff, weaponDir) < 0.9f) return;
-
- List ignoredBodies = new List();
- foreach (Limb limb in character.AnimController.Limbs)
+ else
{
- ignoredBodies.Add(limb.body.FarseerBody);
+ //couldn't equip the item, escape
+ Escape(deltaTime);
+ return false;
}
+ }
+ return true;
+ }
- var pickedBody = Submarine.PickBody(character.SimPosition, enemy.SimPosition, ignoredBodies);
- if (pickedBody != null && !(pickedBody.UserData is Limb)) return;
+ private void Move(float deltaTime)
+ {
+ // Retreat to safety
+ // TODO: aggressive behaviour, chasing?
+ if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
+ {
+ retreatTarget = HumanAIController.ObjectiveManager.GetObjective().FindBestHull();
+ }
+ if (retreatTarget != null)
+ {
+ if (retreatObjective == null || retreatObjective.Target != retreatTarget)
+ {
+ retreatObjective = new AIObjectiveGoTo(retreatTarget, character, false, true);
+ }
+ retreatObjective.TryComplete(deltaTime);
+ }
+ }
- weapon.Use(deltaTime, character);
+ private bool Reload(float deltaTime)
+ {
+ if (WeaponComponent != null && WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
+ {
+ var containedItems = Weapon.ContainedItems;
+ foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
+ {
+ Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
+ if (containedItem == null)
+ {
+ if (reloadWeaponObjective == null)
+ {
+ reloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, Weapon.GetComponent());
+ }
+ }
+ }
+ }
+ if (reloadWeaponObjective != null)
+ {
+ if (reloadWeaponObjective.IsCompleted())
+ {
+ reloadWeaponObjective = null;
+ }
+ else if (!reloadWeaponObjective.CanBeCompleted)
+ {
+ Escape(deltaTime);
+ }
+ else
+ {
+ reloadWeaponObjective.TryComplete(deltaTime);
+ }
+ return false;
+ }
+ return true;
+ }
+
+ private IEnumerable myBodies;
+ private void Attack(float deltaTime)
+ {
+ character.CursorPosition = Enemy.Position;
+ character.SetInput(InputType.Aim, false, true);
+ if (WeaponComponent is MeleeWeapon meleeWeapon)
+ {
+ if (Vector2.DistanceSquared(character.Position, Enemy.Position) <= meleeWeapon.Range * meleeWeapon.Range)
+ {
+ Weapon.Use(deltaTime, character);
+ }
+ }
+ else
+ {
+ if (WeaponComponent is RepairTool repairTool)
+ {
+ if (Vector2.DistanceSquared(character.Position, Enemy.Position) > repairTool.Range * repairTool.Range) { return; }
+ }
+ if (VectorExtensions.Angle(VectorExtensions.Forward(Weapon.body.TransformedRotation), Enemy.Position - character.Position) < MathHelper.PiOver4)
+ {
+ if (myBodies == null)
+ {
+ myBodies = character.AnimController.Limbs.Select(l => l.body.FarseerBody);
+ }
+ var pickedBody = Submarine.PickBody(character.SimPosition, Enemy.SimPosition, myBodies);
+ if (pickedBody != null)
+ {
+ Character target = null;
+ if (pickedBody.UserData is Character c)
+ {
+ target = c;
+ }
+ else if (pickedBody.UserData is Limb limb)
+ {
+ target = limb.character;
+ }
+ if (target != null && target == Enemy)
+ {
+ Weapon.Use(deltaTime, character);
+ }
+ }
+ }
}
}
private void Escape(float deltaTime)
{
- // TODO: just let the find safety run?
- if (escapeObjective == null)
- {
- escapeObjective = new AIObjectiveFindSafety(character);
- }
-
- if (enemy.AnimController.CurrentHull == character.AnimController.CurrentHull)
- {
- escapeObjective.OverrideCurrentHullSafety = 0.0f;
- }
- else
- {
- escapeObjective.OverrideCurrentHullSafety = null;
- }
-
- escapeObjective.TryComplete(deltaTime);
+ abandon = true;
+ SteeringManager.Reset();
+ HumanAIController.ObjectiveManager.GetObjective().Priority = 100;
}
- public override bool IsCompleted()
- {
- return enemy == null || enemy.Removed || enemy.IsDead || coolDownTimer <= 0.0f;
- }
-
- public override float GetPriority(AIObjectiveManager objectiveManager)
- {
- if (enemy == null || enemy.Removed)
- {
- return 0.0f;
- }
-
- if (objectiveManager.CurrentOrder == this)
- {
- return AIObjectiveManager.OrderPriority;
- }
-
- //clamp the strength to the health of this character
- //(it doesn't make a difference whether the enemy does 200 or 600 damage, it's one hit kill anyway)
-
- float enemyDanger = Math.Min(Math.Max(CalculateEnemyStrength(), MaxEnemyDamage), character.Health) + enemy.Health / 10.0f;
-
- if (enemy.AIController is EnemyAIController enemyAI)
- {
- if (enemyAI.SelectedAiTarget == character.AiTarget) enemyDanger *= 2.0f;
- }
-
- return Math.Max(enemyDanger, AIObjectiveManager.OrderPriority);
- }
+ public override bool IsCompleted() => Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0.0f;
+ public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
+ public override float GetPriority(AIObjectiveManager objectiveManager) => Enemy == null || Enemy.Removed || Enemy.IsDead ? 0 : 100;
public override bool IsDuplicate(AIObjective otherObjective)
{
- AIObjectiveCombat objective = otherObjective as AIObjectiveCombat;
- if (objective == null) return false;
-
- return objective.enemy == enemy;
+ if (!(otherObjective is AIObjectiveCombat objective)) return false;
+ return objective.Enemy == Enemy;
}
- private float CalculateEnemyStrength()
- {
- float enemyStrength = 0;
- AttackContext currentContext = character.GetAttackContext();
- foreach (Limb limb in enemy.AnimController.Limbs)
- {
- if (limb.attack == null) continue;
- if (!limb.attack.IsValidContext(currentContext)) { continue; }
- if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
- enemyStrength += limb.attack.GetTotalDamage(false);
- }
- return enemyStrength;
- }
+ //private float CalculateEnemyStrength()
+ //{
+ // float enemyStrength = 0;
+ // AttackContext currentContext = character.GetAttackContext();
+ // foreach (Limb limb in Enemy.AnimController.Limbs)
+ // {
+ // if (limb.attack == null) continue;
+ // if (!limb.attack.IsValidContext(currentContext)) { continue; }
+ // if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
+ // enemyStrength += limb.attack.GetTotalDamage(false);
+ // }
+ // return enemyStrength;
+ //}
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs
index fd208bb1a..ad375e55e 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveExtinguishFire.cs
@@ -32,7 +32,7 @@ namespace Barotrauma
float dist = Math.Abs(character.WorldPosition.X - targetHull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - targetHull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 10000, dist));
float severityFactor = MathHelper.Lerp(0, 1, MathHelper.Clamp(targetHull.FireSources.Sum(fs => fs.Size.X) / targetHull.Size.X, 0, 1));
- return MathHelper.Clamp(priority * severityFactor * distanceFactor, 0, 100);
+ return MathHelper.Clamp(Priority * severityFactor * distanceFactor, 0, 100);
}
public override bool IsCompleted()
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs
index 51569bd04..b2158a614 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindDivingGear.cs
@@ -62,7 +62,7 @@ namespace Barotrauma
if (containedItem == null) continue;
if (containedItem.Condition <= 0.0f)
{
- containedItem.Drop();
+ containedItem.Drop(character);
}
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
{
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs
index e7beb84bf..6ad1eeca8 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFindSafety.cs
@@ -16,18 +16,17 @@ namespace Barotrauma
const float priorityIncrease = 25;
const float priorityDecrease = 10;
const float SearchHullInterval = 3.0f;
+ const float clearUnreachableInterval = 30;
private List unreachable = new List();
private float currenthullSafety;
-
+ private float unreachableClearTimer;
private float searchHullTimer;
private AIObjectiveGoTo goToObjective;
private AIObjective divingGearObjective;
- public float? OverrideCurrentHullSafety;
-
public AIObjectiveFindSafety(Character character) : base(character, "") { }
public override bool IsCompleted() => false;
@@ -52,7 +51,7 @@ namespace Barotrauma
if (divingGearObjective.IsCompleted())
{
divingGearObjective = null;
- priority = 0;
+ Priority = 0;
}
else if (divingGearObjective.CanBeCompleted)
{
@@ -61,6 +60,16 @@ namespace Barotrauma
}
}
+ if (unreachableClearTimer > 0)
+ {
+ unreachableClearTimer -= deltaTime;
+ }
+ else
+ {
+ unreachableClearTimer = clearUnreachableInterval;
+ unreachable.Clear();
+ }
+
if (searchHullTimer > 0.0f)
{
searchHullTimer -= deltaTime;
@@ -147,16 +156,35 @@ namespace Barotrauma
}
}
- private Hull FindBestHull()
+ public Hull FindBestHull()
{
Hull bestHull = character.CurrentHull;
float bestValue = currenthullSafety;
foreach (Hull hull in Hull.hullList)
{
- if (unreachable.Contains(hull)) { continue; }
if (hull.Submarine == null) { continue; }
float hullSafety = 0;
- if (character.Submarine == null)
+ if (character.Submarine != null && SteeringManager == PathSteering)
+ {
+ // Inside or outside near the sub
+ if (unreachable.Contains(hull)) { continue; }
+ if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
+ hullSafety = HumanAIController.GetHullSafety(hull, character);
+ var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
+ int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
+ // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
+ float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
+ float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
+ hullSafety *= distanceFactor;
+ // Each unsafe node reduces the hull safety value.
+ hullSafety /= 1 + unsafeNodes;
+ // If the target is not inside a friendly submarine, considerably reduce the hull safety.
+ if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
+ {
+ hullSafety /= 10;
+ }
+ }
+ else
{
// Outside
if (hull.RoomName?.ToLowerInvariant() == "airlock")
@@ -177,7 +205,8 @@ namespace Barotrauma
}
// Huge preference for closer targets
- float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, 100000, Vector2.Distance(character.WorldPosition, hull.WorldPosition)));
+ float distance = Vector2.DistanceSquared(character.WorldPosition, hull.WorldPosition);
+ float distanceFactor = MathHelper.Lerp(1, 0.2f, MathUtils.InverseLerp(0, MathUtils.Pow(100000, 2), distance));
hullSafety *= distanceFactor;
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC)
@@ -185,26 +214,6 @@ namespace Barotrauma
hullSafety /= 10;
}
}
- else
- {
- // Inside
- // Not connected.
- if (!character.Submarine.GetConnectedSubs().Contains(hull.Submarine)) { continue; }
- hullSafety = HumanAIController.GetHullSafety(hull, character);
- var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
- int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
- // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
- float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
- float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
- hullSafety *= distanceFactor;
- // Each unsafe node reduces the hull safety value.
- hullSafety /= 1 + unsafeNodes;
- // If the target is not inside a friendly submarine, considerably reduce the hull safety.
- if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
- {
- hullSafety /= 10;
- }
- }
if (hullSafety > bestValue)
{
bestHull = hull;
@@ -224,24 +233,24 @@ namespace Barotrauma
if (character.CurrentHull == null)
{
currenthullSafety = 0;
- priority = 5;
+ Priority = 5;
return;
}
- if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { priority = 100; }
- currenthullSafety = OverrideCurrentHullSafety ?? HumanAIController.GetHullSafety(character.CurrentHull);
+ if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
+ currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
{
- priority -= priorityDecrease * deltaTime;
+ Priority -= priorityDecrease * deltaTime;
}
else
{
float dangerFactor = (100 - currenthullSafety) / 100;
- priority += dangerFactor * priorityIncrease * deltaTime;
+ Priority += dangerFactor * priorityIncrease * deltaTime;
}
- priority = MathHelper.Clamp(priority, 0, 100);
+ Priority = MathHelper.Clamp(Priority, 0, 100);
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
{
- priority = Math.Max(priority, AIObjectiveManager.OrderPriority + 10);
+ Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
}
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs
index 517076479..8a29fefcd 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeak.cs
@@ -11,6 +11,7 @@ namespace Barotrauma
public override string DebugTag => "fix leak";
public override bool KeepDivingGearOn => true;
+ public override bool ForceRun => true;
private readonly Gap leak;
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs
index 690db42c8..0e814171e 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveFixLeaks.cs
@@ -10,6 +10,7 @@ namespace Barotrauma
{
public override string DebugTag => "fix leaks";
public override bool KeepDivingGearOn => true;
+ public override bool ForceRun => true;
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs
index d3ded6a94..68428100b 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGetItem.cs
@@ -144,7 +144,7 @@ namespace Barotrauma
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List() { InvSlotType.Any })) continue;
//if everything else fails, simply drop the existing item
- character.Inventory.Items[i].Drop();
+ character.Inventory.Items[i].Drop(character);
}
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs
index 5a88f2646..f4a2ab366 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveGoTo.cs
@@ -45,17 +45,22 @@ namespace Barotrauma
get
{
bool canComplete = !cannotReach && !abandon;
- if (FollowControlledCharacter && Character.Controlled == null) { canComplete = false; }
- else if (Target != null && Target.Removed) { canComplete = false; }
- else if (repeat || waitUntilPathUnreachable > 0.0f) { canComplete = true; }
- else if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering)
+ if (canComplete)
{
- //path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable TODO: add a timer?
- if (pathSteering.CurrentPath == null) { canComplete = true; }
- else if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { canComplete = false; }
- if (canComplete)
+ if (FollowControlledCharacter && Character.Controlled == null)
{
- canComplete = !pathSteering.CurrentPath.Unreachable;
+ canComplete = false;
+ }
+ else if (Target != null && Target.Removed)
+ {
+ canComplete = false;
+ }
+ else if (!repeat && waitUntilPathUnreachable < 0)
+ {
+ if (SteeringManager == PathSteering && PathSteering.CurrentPath != null)
+ {
+ canComplete = !PathSteering.CurrentPath.Unreachable;
+ }
}
}
if (!canComplete)
@@ -144,9 +149,9 @@ namespace Barotrauma
}
else
{
- var indoorSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
- bool targetIsOutside = (Target != null && Target.Submarine == null) || (indoorSteering != null && indoorSteering.CurrentPath != null && indoorSteering.CurrentPath.HasOutdoorsNodes);
- if (targetIsOutside && !AllowGoingOutside)
+ bool targetIsOutside = (Target != null && Target.Submarine == null) ||
+ (SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.HasOutdoorsNodes);
+ if (targetIsOutside && character.CurrentHull != null && !AllowGoingOutside)
{
cannotReach = true;
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs
index 8c2fd39ac..7d0794a5a 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveIdle.cs
@@ -11,6 +11,12 @@ namespace Barotrauma
public override string DebugTag => "idle";
const float WallAvoidDistance = 150.0f;
+ private readonly float newTargetIntervalMin = 5;
+ private readonly float newTargetIntervalMax = 15;
+ private readonly float standStillMin = 1;
+ private readonly float standStillMax = 10;
+ private readonly float walkDurationMin = 3;
+ private readonly float walkDurationMax = 10;
private Hull currentTarget;
private float newTargetTimer;
@@ -46,7 +52,10 @@ namespace Barotrauma
character.SelectedConstruction = null;
}
- if (currentTarget == null && (IsForbidden(character.CurrentHull) || HumanAIController.UnsafeHulls.Contains(character.CurrentHull)))
+ bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) ||
+ (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
+
+ if (currentTargetIsInvalid || (currentTarget == null && IsForbidden(character.CurrentHull)))
{
newTargetTimer = 0;
standStillTimer = 0;
@@ -70,7 +79,7 @@ namespace Barotrauma
PathSteering.SetPath(path);
}
- newTargetTimer = currentTarget == null ? 5.0f : 15.0f;
+ newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax);
}
newTargetTimer -= deltaTime;
@@ -79,20 +88,20 @@ namespace Barotrauma
// - if reached the end of the path
// - if the target is unreachable
// - if the path requires going outside
- if (PathSteering == null || (PathSteering.CurrentPath != null &&
+ if (SteeringManager != PathSteering || (PathSteering.CurrentPath != null &&
(PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
{
standStillTimer -= deltaTime;
if (standStillTimer > 0.0f)
{
- walkDuration = Rand.Range(1.0f, 5.0f);
+ walkDuration = Rand.Range(walkDurationMin, walkDurationMax);
PathSteering.Reset();
return;
}
if (standStillTimer < -walkDuration)
{
- standStillTimer = Rand.Range(1.0f, 10.0f);
+ standStillTimer = Rand.Range(standStillMin, standStillMax);
}
//steer away from edges of the hull
@@ -128,12 +137,11 @@ namespace Barotrauma
}
character.AIController.SteeringManager.SteeringWander();
- if (!character.IsClimbing)
+ if (!character.IsClimbing && !character.AnimController.InWater)
{
//reset vertical steering to prevent dropping down from platforms etc
character.AIController.SteeringManager.ResetY();
}
-
return;
}
@@ -150,6 +158,7 @@ namespace Barotrauma
{
var idCard = character.Inventory.FindItemByIdentifier("idcard");
Hull targetHull = null;
+ bool isCurrentHullOK = !HumanAIController.UnsafeHulls.Contains(character.CurrentHull) && !IsForbidden(character.CurrentHull);
//random chance of navigating back to the room where the character spawned
if (Rand.Int(5) == 1 && idCard != null)
{
@@ -186,9 +195,13 @@ namespace Barotrauma
continue;
}
}
- // Check that there is no unsafe or forbidden hulls on the way to the target
- var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
- if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
+ if (isCurrentHullOK)
+ {
+ // Check that there is no unsafe or forbidden hulls on the way to the target
+ // Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull.
+ var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
+ if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
+ }
// If we want to do a steering check, we should do it here, before setting the path
//if (path.Cost > 1000.0f) { continue; }
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs
index b456d9f62..31b2a9015 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveManager.cs
@@ -56,23 +56,6 @@ namespace Barotrauma
DelayedObjectives.Add(objective, coroutine);
}
- public Dictionary DelayedObjectives { get; private set; } = new Dictionary();
- public void AddObjective(AIObjective objective, float delay, Action callback = null)
- {
- if (DelayedObjectives.TryGetValue(objective, out CoroutineHandle coroutine))
- {
- CoroutineManager.StopCoroutines(coroutine);
- DelayedObjectives.Remove(objective);
- }
- coroutine = CoroutineManager.InvokeAfter(() =>
- {
- DelayedObjectives.Remove(objective);
- AddObjective(objective);
- callback?.Invoke();
- }, delay);
- DelayedObjectives.Add(objective, coroutine);
- }
-
public T GetObjective() where T : AIObjective
{
foreach (AIObjective objective in Objectives)
@@ -200,27 +183,6 @@ namespace Barotrauma
if (order.TargetItemComponent == null) return;
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
break;
- case "chargebatteries":
- currentOrder = new AIObjectiveChargeBatteries(character, option);
- break;
- case "rescue":
- currentOrder = new AIObjectiveRescueAll(character);
- break;
- case "repairsystems":
- currentOrder = new AIObjectiveRepairItems(character) { RequireAdequateSkills = option != "all" };
- break;
- case "pumpwater":
- currentOrder = new AIObjectivePumpWater(character, option);
- break;
- case "extinguishfires":
- currentOrder = new AIObjectiveExtinguishFires(character);
- break;
- case "steer":
- var steering = (order?.TargetEntity as Item)?.GetComponent();
- if (steering != null) steering.PosToMaintain = steering.Item.Submarine?.WorldPosition;
- if (order.TargetItemComponent == null) return;
- currentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
- break;
default:
if (order.TargetItemComponent == null) return;
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs
index 6b244961a..369ad5b5c 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveOperateItem.cs
@@ -135,7 +135,7 @@ namespace Barotrauma
if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) ||
!character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List() { InvSlotType.Any }))
{
- character.Inventory.Items[i].Drop();
+ character.Inventory.Items[i].Drop(character);
}
}
if (character.Inventory.TryPutItem(component.Item, i, true, false, character))
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs
index 8a6b47f1f..fe0730eff 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/AI/Objectives/AIObjectiveRepairItem.cs
@@ -35,7 +35,7 @@ namespace Barotrauma
float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition);
float successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
- float baseLevel = Math.Max(priority + isSelected, 1);
+ float baseLevel = Math.Max(Priority + isSelected, 1);
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100);
}
@@ -84,6 +84,7 @@ namespace Barotrauma
}
if (character.CanInteractWith(Item))
{
+ OperateRepairTool(deltaTime);
foreach (Repairable repairable in Item.Repairables)
{
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
@@ -123,5 +124,33 @@ namespace Barotrauma
AddSubObjective(goToObjective);
}
}
+
+ private void OperateRepairTool(float deltaTime)
+ {
+ // Operate repair tool, if required.
+ foreach (Repairable repairable in Item.Repairables)
+ {
+ foreach (var kvp in repairable.requiredItems)
+ {
+ foreach (RelatedItem requiredItem in kvp.Value)
+ {
+ foreach (var item in character.Inventory.Items)
+ {
+ if (requiredItem.MatchesItem(item))
+ {
+ var repairTool = item.GetComponent();
+ if (repairTool != null)
+ {
+ character.CursorPosition = Item.Position;
+ character.SetInput(InputType.Aim, false, true);
+ repairTool.Use(deltaTime, character);
+ return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs
index 17a67ca3f..dff106cfb 100644
--- a/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs
+++ b/Barotrauma/BarotraumaShared/Source/Characters/Health/CharacterHealth.cs
@@ -549,8 +549,9 @@ namespace Barotrauma
limbHealths[i].Afflictions.RemoveAt(j);
}
}
- foreach (Affliction affliction in limbHealths[i].Afflictions)
+ for (int j = limbHealths[i].Afflictions.Count - 1; j >= 0; j--)
{
+ var affliction = limbHealths[i].Afflictions[j];
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == i);
affliction.Update(this, targetLimb, deltaTime);
affliction.DamagePerSecondTimer += deltaTime;
diff --git a/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs b/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs
index 13c0a8d55..b75f7012e 100644
--- a/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs
+++ b/Barotrauma/BarotraumaShared/Source/Events/EventManager.cs
@@ -120,18 +120,30 @@ namespace Barotrauma
private void CreateEvents()
{
+ //don't create new events if docked to the start oupost
+ if (Level.Loaded?.StartOutpost != null &&
+ Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))
+ {
+ return;
+ }
+
for (int i = selectedEventSets.Count - 1; i >= 0; i--)
{
ScriptedEventSet eventSet = selectedEventSets[i];
+ float distFromStart = Vector2.Distance(Submarine.MainSub.WorldPosition, level.StartPosition);
+ float distFromEnd = Vector2.Distance(Submarine.MainSub.WorldPosition, level.EndPosition);
+
float distanceTraveled = MathHelper.Clamp(
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
0.0f, 1.0f);
- if (Level.Loaded?.StartOutpost != null &&
- Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))
+ //don't create new events if within 50 meters of the start/end of the level
+ if (distanceTraveled <= 0.0f ||
+ distFromStart * Physics.DisplayToRealWorldRatio < 50.0f ||
+ distFromEnd * Physics.DisplayToRealWorldRatio < 50.0f)
{
- distanceTraveled = 0.0f;
+ continue;
}
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
diff --git a/Barotrauma/BarotraumaShared/Source/GameSettings.cs b/Barotrauma/BarotraumaShared/Source/GameSettings.cs
index 49e1ca3b0..e8b625621 100644
--- a/Barotrauma/BarotraumaShared/Source/GameSettings.cs
+++ b/Barotrauma/BarotraumaShared/Source/GameSettings.cs
@@ -65,12 +65,6 @@ namespace Barotrauma
public int ParticleLimit { get; set; }
- public float LightMapScale { get; set; }
- public bool SpecularityEnabled { get; set; }
- public bool ChromaticAberrationEnabled { get; set; }
-
- public bool MuteOnFocusLost { get; set; }
-
public int ParticleLimit { get; set; }
public float LightMapScale { get; set; }
@@ -405,8 +399,6 @@ namespace Barotrauma
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
- AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
-
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
@@ -620,98 +612,6 @@ namespace Barotrauma
}
}
- TextManager.LoadTextPacks(SelectedContentPackages);
-
- //display error messages after all content packages have been loaded
- //to make sure the package that contains text files has been loaded before we attempt to use TextManager
- foreach (string missingPackagePath in missingPackagePaths)
- {
- DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
- }
- foreach (ContentPackage incompatiblePackage in incompatiblePackages)
- {
- DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
- .Replace("[packagename]", incompatiblePackage.Name)
- .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
- .Replace("[gameversion]", GameMain.Version.ToString()));
- }
- foreach (ContentPackage contentPackage in SelectedContentPackages)
- {
- foreach (ContentFile file in contentPackage.Files)
- {
- if (!System.IO.File.Exists(file.Path))
- {
- DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
- continue;
- }
- ToolBox.IsProperFilenameCase(file.Path);
- }
- }
-
- TextManager.LoadTextPacks(SelectedContentPackages);
-
- //display error messages after all content packages have been loaded
- //to make sure the package that contains text files has been loaded before we attempt to use TextManager
- foreach (string missingPackagePath in missingPackagePaths)
- {
- DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
- }
- foreach (ContentPackage incompatiblePackage in incompatiblePackages)
- {
- DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
- .Replace("[packagename]", incompatiblePackage.Name)
- .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
- .Replace("[gameversion]", GameMain.Version.ToString()));
- }
- foreach (ContentPackage contentPackage in SelectedContentPackages)
- {
- foreach (ContentFile file in contentPackage.Files)
- {
- if (!System.IO.File.Exists(file.Path))
- {
- DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
- continue;
- }
- ToolBox.IsProperFilenameCase(file.Path);
- }
- }
-
- TextManager.LoadTextPacks(SelectedContentPackages);
-
- //display error messages after all content packages have been loaded
- //to make sure the package that contains text files has been loaded before we attempt to use TextManager
- foreach (string missingPackagePath in missingPackagePaths)
- {
- DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
- }
- foreach (ContentPackage incompatiblePackage in incompatiblePackages)
- {
- DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
- .Replace("[packagename]", incompatiblePackage.Name)
- .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
- .Replace("[gameversion]", GameMain.Version.ToString()));
- }
- foreach (ContentPackage contentPackage in SelectedContentPackages)
- {
- foreach (ContentFile file in contentPackage.Files)
- {
- if (!System.IO.File.Exists(file.Path))
- {
- DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
- continue;
- }
- ToolBox.IsProperFilenameCase(file.Path);
- }
- }
- if (!SelectedContentPackages.Any())
- {
- var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
- if (availablePackage != null)
- {
- SelectedContentPackages.Add(availablePackage);
- }
- }
-
//save to get rid of the invalid selected packages in the config file
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
}
@@ -1084,6 +984,369 @@ namespace Barotrauma
}
#endregion
+ #region Save PlayerConfig
+ public void SaveNewPlayerConfig()
+ {
+ XDocument doc = new XDocument();
+ UnsavedSettings = false;
+
+ if (doc.Root == null)
+ {
+ doc.Add(new XElement("config"));
+ }
+
+ doc.Root.Add(
+ new XAttribute("language", TextManager.Language),
+ new XAttribute("masterserverurl", MasterServerUrl),
+ new XAttribute("autocheckupdates", AutoCheckUpdates),
+ new XAttribute("musicvolume", musicVolume),
+ new XAttribute("soundvolume", soundVolume),
+ new XAttribute("verboselogging", VerboseLogging),
+ new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
+ new XAttribute("enablesplashscreen", EnableSplashScreen),
+ new XAttribute("usesteammatchmaking", useSteamMatchmaking),
+ new XAttribute("quickstartsub", QuickStartSubmarineName),
+ new XAttribute("requiresteamauthentication", requireSteamAuthentication),
+ new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems),
+ new XAttribute("aimassistamount", aimAssistAmount));
+
+ if (!ShowUserStatisticsPrompt)
+ {
+ doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
+ }
+
+ XElement gMode = doc.Root.Element("graphicsmode");
+ if (gMode == null)
+ {
+ gMode = new XElement("graphicsmode");
+ doc.Root.Add(gMode);
+ }
+ if (GraphicsWidth == 0 || GraphicsHeight == 0)
+ {
+ gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
+ }
+ else
+ {
+ gMode.ReplaceAttributes(
+ new XAttribute("width", GraphicsWidth),
+ new XAttribute("height", GraphicsHeight),
+ new XAttribute("vsync", VSyncEnabled),
+ new XAttribute("displaymode", windowMode));
+ }
+
+ XElement gSettings = doc.Root.Element("graphicssettings");
+ if (gSettings == null)
+ {
+ gSettings = new XElement("graphicssettings");
+ doc.Root.Add(gSettings);
+ }
+
+ gSettings.ReplaceAttributes(
+ new XAttribute("particlelimit", ParticleLimit),
+ new XAttribute("lightmapscale", LightMapScale),
+ new XAttribute("specularity", SpecularityEnabled),
+ new XAttribute("chromaticaberration", ChromaticAberrationEnabled),
+ new XAttribute("losmode", LosMode),
+ new XAttribute("hudscale", HUDScale),
+ new XAttribute("inventoryscale", InventoryScale));
+
+ foreach (ContentPackage contentPackage in SelectedContentPackages)
+ {
+ if (contentPackage.Path.Contains(vanillaContentPackagePath))
+ {
+ doc.Root.Add(new XElement("contentpackage", new XAttribute("path", contentPackage.Path)));
+ break;
+ }
+ }
+
+ var keyMappingElement = new XElement("keymapping");
+ doc.Root.Add(keyMappingElement);
+ for (int i = 0; i < keyMapping.Length; i++)
+ {
+ if (keyMapping[i].MouseButton == null)
+ {
+ keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].Key));
+ }
+ else
+ {
+ keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].MouseButton));
+ }
+ }
+
+ var gameplay = new XElement("gameplay");
+ var jobPreferences = new XElement("jobpreferences");
+ foreach (string jobName in JobPreferences)
+ {
+ jobPreferences.Add(new XElement("job", new XAttribute("identifier", jobName)));
+ }
+ gameplay.Add(jobPreferences);
+ doc.Root.Add(gameplay);
+
+ var playerElement = new XElement("player",
+ new XAttribute("name", defaultPlayerName ?? ""),
+ new XAttribute("headindex", CharacterHeadIndex),
+ new XAttribute("gender", CharacterGender),
+ new XAttribute("race", CharacterRace),
+ new XAttribute("hairindex", CharacterHairIndex),
+ new XAttribute("beardindex", CharacterBeardIndex),
+ new XAttribute("moustacheindex", CharacterMoustacheIndex),
+ new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
+ doc.Root.Add(playerElement);
+
+ XmlWriterSettings settings = new XmlWriterSettings
+ {
+ Indent = true,
+ OmitXmlDeclaration = true,
+ NewLineOnAttributes = true
+ };
+
+ try
+ {
+ using (var writer = XmlWriter.Create(savePath, settings))
+ {
+ doc.WriteTo(writer);
+ writer.Flush();
+ }
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError("Saving game settings failed.", e);
+ GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
+ "Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace);
+ }
+ }
+ #endregion
+
+ #region Load PlayerConfig
+ // TODO: DRY
+ public void LoadPlayerConfig()
+ {
+ XDocument doc = XMLExtensions.LoadXml(playerSavePath);
+
+ if (doc == null || doc.Root == null)
+ {
+ ShowUserStatisticsPrompt = true;
+ SaveNewPlayerConfig();
+ return;
+ }
+
+ Language = doc.Root.GetAttributeString("language", Language);
+ AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates);
+ sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", true);
+
+ XElement graphicsMode = doc.Root.Element("graphicsmode");
+ GraphicsWidth = graphicsMode.GetAttributeInt("width", GraphicsWidth);
+ GraphicsHeight = graphicsMode.GetAttributeInt("height", GraphicsHeight);
+ VSyncEnabled = graphicsMode.GetAttributeBool("vsync", VSyncEnabled);
+
+ XElement graphicsSettings = doc.Root.Element("graphicssettings");
+ ParticleLimit = graphicsSettings.GetAttributeInt("particlelimit", ParticleLimit);
+ LightMapScale = MathHelper.Clamp(graphicsSettings.GetAttributeFloat("lightmapscale", LightMapScale), 0.1f, 1.0f);
+ SpecularityEnabled = graphicsSettings.GetAttributeBool("specularity", SpecularityEnabled);
+ ChromaticAberrationEnabled = graphicsSettings.GetAttributeBool("chromaticaberration", ChromaticAberrationEnabled);
+ HUDScale = graphicsSettings.GetAttributeFloat("hudscale", HUDScale);
+ InventoryScale = graphicsSettings.GetAttributeFloat("inventoryscale", InventoryScale);
+ var losModeStr = graphicsSettings.GetAttributeString("losmode", "Transparent");
+ if (!Enum.TryParse(losModeStr, out losMode))
+ {
+ losMode = LosMode.Transparent;
+ }
+
+#if CLIENT
+ if (GraphicsWidth == 0 || GraphicsHeight == 0)
+ {
+ GraphicsWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
+ GraphicsHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
+ }
+#endif
+
+ var windowModeStr = graphicsMode.GetAttributeString("displaymode", "Fullscreen");
+ if (!Enum.TryParse(windowModeStr, out windowMode))
+ {
+ windowMode = WindowMode.Fullscreen;
+ }
+
+ XElement audioSettings = doc.Root.Element("audio");
+ if (audioSettings != null)
+ {
+ SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume);
+ MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume);
+ VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
+ string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled");
+ VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", "");
+ NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45);
+ var voiceSetting = VoiceMode.Disabled;
+ if (Enum.TryParse(voiceSettingStr, out voiceSetting))
+ {
+ VoiceSetting = voiceSetting;
+ }
+ }
+
+ useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", useSteamMatchmaking);
+ requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", requireSteamAuthentication);
+
+ EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
+
+ AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount);
+
+ foreach (XElement subElement in doc.Root.Elements())
+ {
+ switch (subElement.Name.ToString().ToLowerInvariant())
+ {
+ case "keymapping":
+ foreach (XAttribute attribute in subElement.Attributes())
+ {
+ if (Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType))
+ {
+ if (int.TryParse(attribute.Value.ToString(), out int mouseButton))
+ {
+ keyMapping[(int)inputType] = new KeyOrMouse(mouseButton);
+ }
+ else
+ {
+ if (Enum.TryParse(attribute.Value.ToString(), true, out Keys key))
+ {
+ keyMapping[(int)inputType] = new KeyOrMouse(key);
+ }
+ }
+ }
+ }
+ break;
+ case "gameplay":
+ jobPreferences = new List();
+ foreach (XElement ele in subElement.Element("jobpreferences").Elements("job"))
+ {
+ string jobIdentifier = ele.GetAttributeString("identifier", "");
+ if (string.IsNullOrEmpty(jobIdentifier)) continue;
+ jobPreferences.Add(jobIdentifier);
+ }
+ break;
+ case "player":
+ defaultPlayerName = subElement.GetAttributeString("name", defaultPlayerName);
+ CharacterHeadIndex = subElement.GetAttributeInt("headindex", CharacterHeadIndex);
+ if (Enum.TryParse(subElement.GetAttributeString("gender", "none"), true, out Gender g))
+ {
+ CharacterGender = g;
+ }
+ if (Enum.TryParse(subElement.GetAttributeString("race", "white"), true, out Race r))
+ {
+ CharacterRace = r;
+ }
+ else
+ {
+ CharacterRace = Race.White;
+ }
+ CharacterHairIndex = subElement.GetAttributeInt("hairindex", CharacterHairIndex);
+ CharacterBeardIndex = subElement.GetAttributeInt("beardindex", CharacterBeardIndex);
+ CharacterMoustacheIndex = subElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex);
+ CharacterFaceAttachmentIndex = subElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex);
+ break;
+ case "tutorials":
+ foreach (XElement tutorialElement in subElement.Elements())
+ {
+ CompletedTutorialNames.Add(tutorialElement.GetAttributeString("name", ""));
+ }
+ break;
+ }
+ }
+
+ foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
+ {
+ if (keyMapping[(int)inputType] == null)
+ {
+ DebugConsole.ThrowError("Key binding for the input type \"" + inputType + " not set!");
+ keyMapping[(int)inputType] = new KeyOrMouse(Keys.D1);
+ }
+ }
+
+ UnsavedSettings = false;
+
+ selectedContentPackagePaths = new HashSet();
+
+ foreach (XElement subElement in doc.Root.Elements())
+ {
+ switch (subElement.Name.ToString().ToLowerInvariant())
+ {
+ case "contentpackage":
+ string path = System.IO.Path.GetFullPath(subElement.GetAttributeString("path", ""));
+ selectedContentPackagePaths.Add(path);
+ break;
+ }
+ }
+
+ LoadContentPackages(selectedContentPackagePaths);
+ }
+
+ public void ReloadContentPackages()
+ {
+ LoadContentPackages(selectedContentPackagePaths);
+ }
+
+ private void LoadContentPackages(IEnumerable contentPackagePaths)
+ {
+ var missingPackagePaths = new List();
+ var incompatiblePackages = new List();
+ SelectedContentPackages.Clear();
+ foreach (string path in contentPackagePaths)
+ {
+ var matchingContentPackage = ContentPackage.List.Find(cp => System.IO.Path.GetFullPath(cp.Path) == path);
+
+ if (matchingContentPackage == null)
+ {
+ missingPackagePaths.Add(path);
+ }
+ else if (!matchingContentPackage.IsCompatible())
+ {
+ incompatiblePackages.Add(matchingContentPackage);
+ }
+ else
+ {
+ SelectedContentPackages.Add(matchingContentPackage);
+ }
+ }
+
+ TextManager.LoadTextPacks(SelectedContentPackages);
+
+ foreach (ContentPackage contentPackage in SelectedContentPackages)
+ {
+ foreach (ContentFile file in contentPackage.Files)
+ {
+ if (!System.IO.File.Exists(file.Path))
+ {
+ DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
+ continue;
+ }
+ ToolBox.IsProperFilenameCase(file.Path);
+ }
+ }
+ if (!SelectedContentPackages.Any())
+ {
+ var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
+ if (availablePackage != null)
+ {
+ SelectedContentPackages.Add(availablePackage);
+ }
+ }
+
+ //save to get rid of the invalid selected packages in the config file
+ if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
+
+ //display error messages after all content packages have been loaded
+ //to make sure the package that contains text files has been loaded before we attempt to use TextManager
+ foreach (string missingPackagePath in missingPackagePaths)
+ {
+ DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
+ }
+ foreach (ContentPackage incompatiblePackage in incompatiblePackages)
+ {
+ DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
+ .Replace("[packagename]", incompatiblePackage.Name)
+ .Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
+ .Replace("[gameversion]", GameMain.Version.ToString()));
+ }
+ }
+ #endregion
+
#region Save PlayerConfig
public void SaveNewPlayerConfig()
{
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs
index 593b02460..5d3f4dcfc 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Holdable.cs
@@ -438,7 +438,7 @@ namespace Barotrauma.Items.Components
GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
}
#endif
- item.Drop();
+ item.Drop(character);
}
AttachToWall();
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs
index 400e69223..2885ca95f 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/RepairTool.cs
@@ -294,9 +294,10 @@ namespace Barotrauma.Items.Components
character.CursorPosition = leak.Position;
character.SetInput(InputType.Aim, false, true);
- if (VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
+ // Press the trigger only when the tool is approximately facing the target.
+ // If the character is climbing, ignore the check, because we cannot aim while climbing.
+ if (character.IsClimbing || VectorExtensions.Angle(VectorExtensions.Forward(item.body.TransformedRotation), fromItemToLeak) < MathHelper.PiOver4)
{
- // Press the trigger only when the tool is approximately facing the target.
Use(deltaTime, character);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs
index 9fd196905..b6107eada 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Holdable/Throwable.cs
@@ -105,7 +105,7 @@ namespace Barotrauma.Items.Components
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
- item.Drop();
+ item.Drop(picker);
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs
index 97c950c1c..11a3ea4e0 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/ItemContainer.cs
@@ -279,7 +279,7 @@ namespace Barotrauma.Items.Components
foreach (Item item in Inventory.Items)
{
if (item == null) continue;
- item.Drop();
+ item.Drop(null);
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
index 49e2b38a0..a3af545f4 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Fabricator.cs
@@ -420,7 +420,7 @@ namespace Barotrauma.Items.Components
if (inputContainer.Inventory.Items.All(it => it != null))
{
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
- unneededItem?.Drop();
+ unneededItem?.Drop(null);
}
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
index 741306c51..660bc5dd8 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Machines/Reactor.cs
@@ -437,7 +437,7 @@ namespace Barotrauma.Items.Components
{
if (item != null && item.Condition <= 0.0f)
{
- item.Drop();
+ item.Drop(character);
}
}
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
index d8f76c924..320adf9ab 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Projectile.cs
@@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components
private void Launch(Vector2 impulse)
{
- item.Drop();
+ item.Drop(null);
item.body.Enabled = true;
item.body.ApplyLinearImpulse(impulse);
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
{
float rotation = item.body.Rotation;
Vector2 simPositon = item.SimPosition;
- item.Drop();
+ item.Drop(null);
item.body.Enabled = true;
//set the velocity of the body because the OnProjectileCollision method
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs
index 968e06ea2..a74c4f4e2 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Components/Turret.cs
@@ -350,7 +350,7 @@ namespace Barotrauma.Items.Components
{
reload = reloadTime;
- projectile.Drop();
+ projectile.Drop(null);
projectile.body.Dir = 1.0f;
projectile.body.ResetDynamics();
diff --git a/Barotrauma/BarotraumaShared/Source/Items/Item.cs b/Barotrauma/BarotraumaShared/Source/Items/Item.cs
index 2290db26a..29915b1d1 100644
--- a/Barotrauma/BarotraumaShared/Source/Items/Item.cs
+++ b/Barotrauma/BarotraumaShared/Source/Items/Item.cs
@@ -1563,7 +1563,7 @@ namespace Barotrauma
return isCombined;
}
- public void Drop(Character dropper = null)
+ public void Drop(Character dropper)
{
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs
index 5e0b5c4dc..c3d5d2926 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Map/Map.cs
@@ -430,13 +430,6 @@ namespace Barotrauma
}
CurrentLocation.SelectedMissionIndex = missionIndex;
- //the destination must be the same as the destination of the mission
- if (CurrentLocation.SelectedMission != null &&
- CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
- {
- SelectLocation(CurrentLocation.SelectedMission.Locations[1]);
- }
-
SelectedLocation = location;
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
diff --git a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
index 92e6c3a45..44fbb7299 100644
--- a/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
+++ b/Barotrauma/BarotraumaShared/Source/Map/Submarine.cs
@@ -467,6 +467,7 @@ namespace Barotrauma
public List GetConnectedSubs()
{
connectedSubs.Clear();
+ connectedSubs.Add(this);
GetConnectedSubsRecursive(connectedSubs);
return connectedSubs;
@@ -522,6 +523,30 @@ namespace Barotrauma
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
+ else
+ {
+ maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
+ }
+ }
+
+ if (minX < 0.0f && maxX > Level.Loaded.Size.X)
+ {
+ //no walls found at either side, just use the initial spawnpos and hope for the best
+ }
+ else if (minX < 0)
+ {
+ //no wall found at the left side, spawn to the left from the right-side wall
+ spawnPos.X = maxX - minWidth - 100.0f;
+ }
+ else if (maxX > Level.Loaded.Size.X)
+ {
+ //no wall found at right side, spawn to the right from the left-side wall
+ spawnPos.X = minX + minWidth + 100.0f;
+ }
+ else
+ {
+ //walls found at both sides, use their midpoint
+ spawnPos.X = (minX + maxX) / 2;
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
@@ -655,7 +680,7 @@ namespace Barotrauma
}
}
- public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null)
+ public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate customPredicate = null)
{
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
{
@@ -1011,6 +1036,31 @@ namespace Barotrauma
return false;
}
+ ///
+ /// Returns true if the sub is same as the other.
+ ///
+ public bool IsConnectedTo(Submarine otherSub) => this == otherSub || GetConnectedSubs().Contains(otherSub);
+
+ public List GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList);
+ public List GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
+ public List- GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
+
+ public List GetEntities(bool includingConnectedSubs, List list) where T : MapEntity
+ {
+ return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
+ }
+
+ public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs)
+ {
+ if (entity.Submarine == this) { return true; }
+ if (entity.Submarine == null) { return false; }
+ if (includingConnectedSubs)
+ {
+ return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID);
+ }
+ return false;
+ }
+
///
/// Finds the sub whose borders contain the position
///
diff --git a/Barotrauma/BarotraumaShared/Source/PlayerInput.cs b/Barotrauma/BarotraumaShared/Source/PlayerInput.cs
index fc56c8fc7..dac0e7d44 100644
--- a/Barotrauma/BarotraumaShared/Source/PlayerInput.cs
+++ b/Barotrauma/BarotraumaShared/Source/PlayerInput.cs
@@ -160,16 +160,6 @@ namespace Barotrauma
}
#endif
- public void SetState()
- {
- hit = binding.IsHit();
- if (hit) hitQueue = true;
-
- held = binding.IsDown();
- if (held) heldQueue = true;
- }
-#endif
-
public bool Hit
{
get
diff --git a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub
index e6b2f571a..97196e8ec 100644
Binary files a/Barotrauma/BarotraumaShared/Submarines/Dugong.sub and b/Barotrauma/BarotraumaShared/Submarines/Dugong.sub differ
diff --git a/Barotrauma/BarotraumaShared/Submarines/Venture.sub b/Barotrauma/BarotraumaShared/Submarines/Venture.sub
new file mode 100644
index 000000000..8cb2b6a3d
Binary files /dev/null and b/Barotrauma/BarotraumaShared/Submarines/Venture.sub differ