aeafa16...4d3cf73
This commit is contained in:
@@ -214,6 +214,9 @@
|
|||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\SpriteDeformation.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\DeformAnimations\SpriteDeformation.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\Sprite.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\Sprite.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\SpriteSheet.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Sprite\SpriteSheet.cs" />
|
||||||
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\LocalizationCSVtoXML.cs">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</Compile>
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\MathUtils.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\MathUtils.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\TextureLoader.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\TextureLoader.cs" />
|
||||||
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\ToolBox.cs" />
|
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\ToolBox.cs" />
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
@@ -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
|
|
||||||
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -153,6 +153,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
|
<Content Include="Shaders\blurshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\damageshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\deformshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\losshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\postprocess_opengl.fx" />
|
||||||
|
<Content Include="Shaders\watershader_opengl.fx" />
|
||||||
|
<None Include="Shaders\Content_opengl.mgcb" />
|
||||||
<None Include="Content\Effects\blurshader_opengl.xnb">
|
<None Include="Content\Effects\blurshader_opengl.xnb">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
@@ -153,6 +153,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="app.config" />
|
<None Include="app.config" />
|
||||||
|
<Content Include="Shaders\blurshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\damageshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\deformshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\losshader_opengl.fx" />
|
||||||
|
<Content Include="Shaders\postprocess_opengl.fx" />
|
||||||
|
<Content Include="Shaders\watershader_opengl.fx" />
|
||||||
|
<None Include="Shaders\Content_opengl.mgcb" />
|
||||||
<None Include="Content\Effects\blurshader_opengl.xnb">
|
<None Include="Content\Effects\blurshader_opengl.xnb">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
|||||||
+15
@@ -14,6 +14,7 @@ float2 uvTopLeft;
|
|||||||
float2 uvBottomRight;
|
float2 uvBottomRight;
|
||||||
|
|
||||||
float4 tintColor;
|
float4 tintColor;
|
||||||
|
float4 solidColor;
|
||||||
|
|
||||||
struct VertexShaderInput
|
struct VertexShaderInput
|
||||||
{
|
{
|
||||||
@@ -74,6 +75,11 @@ float4 mainPS(VertexShaderOutput input) : COLOR
|
|||||||
return xTexture.Sample(TextureSampler, input.TexCoords) * 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
|
technique DeformShader
|
||||||
{
|
{
|
||||||
pass Pass1
|
pass Pass1
|
||||||
@@ -82,3 +88,12 @@ technique DeformShader
|
|||||||
PixelShader = compile ps_4_0_level_9_1 mainPS();
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+15
@@ -14,6 +14,7 @@ float2 uvTopLeft;
|
|||||||
float2 uvBottomRight;
|
float2 uvBottomRight;
|
||||||
|
|
||||||
float4 tintColor;
|
float4 tintColor;
|
||||||
|
float4 solidColor;
|
||||||
|
|
||||||
struct VertexShaderInput
|
struct VertexShaderInput
|
||||||
{
|
{
|
||||||
@@ -74,6 +75,11 @@ float4 mainPS(VertexShaderOutput input) : COLOR
|
|||||||
return xTexture.Sample(TextureSampler, input.TexCoords) * 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
|
technique DeformShader
|
||||||
{
|
{
|
||||||
pass Pass1
|
pass Pass1
|
||||||
@@ -82,3 +88,12 @@ technique DeformShader
|
|||||||
PixelShader = compile ps_3_0 mainPS();
|
PixelShader = compile ps_3_0 mainPS();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
technique DeformShaderSolidColor
|
||||||
|
{
|
||||||
|
pass Pass1
|
||||||
|
{
|
||||||
|
VertexShader = compile vs_3_0 mainVS();
|
||||||
|
PixelShader = compile ps_3_0 solidColorPS();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -87,52 +87,5 @@ namespace Barotrauma
|
|||||||
// GUI.DrawLine(spriteBatch, pos, new Vector2(Character.CursorWorldPosition.X, -Character.CursorWorldPosition.Y), Color.Yellow, width: 4);
|
// 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ namespace Barotrauma
|
|||||||
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
|
var entityList = Submarine.VisibleEntities ?? Item.ItemList;
|
||||||
|
|
||||||
Item closestItem = null;
|
Item closestItem = null;
|
||||||
float closestItemDistance = aimAssistAmount;
|
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
|
||||||
foreach (MapEntity entity in entityList)
|
foreach (MapEntity entity in entityList)
|
||||||
{
|
{
|
||||||
if (!(entity is Item item))
|
if (!(entity is Item item))
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using System;
|
using System;
|
||||||
@@ -136,7 +137,11 @@ namespace Barotrauma
|
|||||||
brokenItemsCheckTimer = 1.0f;
|
brokenItemsCheckTimer = 1.0f;
|
||||||
foreach (Item item in Item.ItemList)
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,10 +123,7 @@ namespace Barotrauma
|
|||||||
for (int i = 0; i < inputCount; i++)
|
for (int i = 0; i < inputCount; i++)
|
||||||
{
|
{
|
||||||
msg.WriteRangedInteger(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
|
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) ||
|
if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
|
||||||
memInput[i].states.HasFlag(InputNetFlags.Use) ||
|
memInput[i].states.HasFlag(InputNetFlags.Use) ||
|
||||||
memInput[i].states.HasFlag(InputNetFlags.Health) ||
|
memInput[i].states.HasFlag(InputNetFlags.Health) ||
|
||||||
|
|||||||
@@ -436,13 +436,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
AssignOnExecute("ambientlight", (string[] args) =>
|
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));
|
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);
|
NewMessage("Set ambient light color to " + color + ".", Color.White);
|
||||||
});
|
});
|
||||||
AssignRelayToServer("ambientlight", false);
|
AssignRelayToServer("ambientlight", false);
|
||||||
@@ -1050,6 +1052,195 @@ namespace Barotrauma
|
|||||||
TextManager.WriteToCSV();
|
TextManager.WriteToCSV();
|
||||||
NPCConversation.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<string> lines = new List<string>();
|
||||||
|
foreach (MapEntityPrefab me in MapEntityPrefab.List)
|
||||||
|
{
|
||||||
|
lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</" + me.Identifier + ".Name>");
|
||||||
|
lines.Add("<EntityDescription." + me.Identifier + ">" + 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
|
#endif
|
||||||
|
|
||||||
commands.Add(new Command("cleanbuild", "", (string[] args) =>
|
commands.Add(new Command("cleanbuild", "", (string[] args) =>
|
||||||
|
|||||||
@@ -384,12 +384,12 @@ namespace Barotrauma
|
|||||||
Cursor.Draw(spriteBatch, PlayerInput.LatestMousePosition);
|
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);
|
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) *
|
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 = GameMain.GameScreen.PostProcessEffect.Techniques["BlurChromaticAberration"];
|
||||||
GameMain.GameScreen.PostProcessEffect.CurrentTechnique.Passes[0].Apply();
|
GameMain.GameScreen.PostProcessEffect.CurrentTechnique.Passes[0].Apply();
|
||||||
|
|
||||||
|
|||||||
@@ -107,14 +107,37 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
scrollButtonDown.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipVertically);
|
scrollButtonDown.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipVertically);
|
||||||
|
|
||||||
var characterInfo = new CharacterInfo(subElement);
|
if (isSinglePlayer)
|
||||||
characterInfos.Add(characterInfo);
|
{
|
||||||
foreach (XElement invElement in subElement.Elements())
|
chatBox = new ChatBox(guiFrame, isSinglePlayer: true)
|
||||||
{
|
{
|
||||||
if (invElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
|
OnEnterMessage = (textbox, text) =>
|
||||||
characterInfo.InventoryData = invElement;
|
{
|
||||||
break;
|
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);
|
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;
|
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
|
||||||
SetCharacterOrder(null, order, null, Character.Controlled);
|
SetCharacterOrder(null, order, null, Character.Controlled);
|
||||||
|
HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
UserData = order,
|
UserData = order,
|
||||||
@@ -152,12 +176,14 @@ namespace Barotrauma
|
|||||||
Visible = false
|
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,
|
if (invElement.Name.ToString().ToLowerInvariant() != "inventory") continue;
|
||||||
HoverColor = Color.Lerp(order.Color, Color.White, 0.5f),
|
characterInfo.InventoryData = invElement;
|
||||||
ToolTip = order.Name
|
break;
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||||
|
|||||||
@@ -7,10 +7,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
private InfoFrameTab selectedTab;
|
private InfoFrameTab selectedTab;
|
||||||
private GUIButton infoFrame;
|
private GUIButton infoFrame;
|
||||||
/// <summary>
|
|
||||||
/// Determines whether the hotkey for the info button was held down in the previous frame.
|
|
||||||
/// </summary>
|
|
||||||
private bool prevInfoKey;
|
|
||||||
|
|
||||||
private GUIFrame infoFrameContent;
|
private GUIFrame infoFrameContent;
|
||||||
|
|
||||||
@@ -20,11 +16,6 @@ namespace Barotrauma
|
|||||||
get { return roundSummary; }
|
get { return roundSummary; }
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void InitProjSpecific()
|
|
||||||
{
|
|
||||||
prevInfoKey = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ToggleInfoFrame()
|
private bool ToggleInfoFrame()
|
||||||
{
|
{
|
||||||
if (infoFrame == null)
|
if (infoFrame == null)
|
||||||
@@ -46,7 +37,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
infoFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
|
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 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);
|
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 (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();
|
ToggleInfoFrame();
|
||||||
prevInfoKey = PlayerInput.KeyDown(InputType.InfoTab);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
infoFrame?.UpdateManually(deltaTime);
|
infoFrame?.UpdateManually(deltaTime);
|
||||||
|
|||||||
@@ -626,28 +626,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage"));
|
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
|
//spacing
|
||||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null);
|
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
private bool shouldMuffleLooping;
|
||||||
|
|||||||
@@ -598,7 +598,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (selectedSlot == null)
|
if (selectedSlot == null)
|
||||||
{
|
{
|
||||||
draggingItem.Drop();
|
draggingItem.Drop(Character.Controlled);
|
||||||
GUI.PlayUISound(GUISoundType.DropItem);
|
GUI.PlayUISound(GUISoundType.DropItem);
|
||||||
}
|
}
|
||||||
else if (selectedSlot.ParentInventory.Items[selectedSlot.SlotIndex] != draggingItem)
|
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 (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);
|
System.Diagnostics.Debug.Assert(Items[i] == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -849,6 +849,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (GameMain.Client == null) { return; }
|
if (GameMain.Client == null) { return; }
|
||||||
|
|
||||||
|
if (parentInventory != null || body == null || !body.Enabled || Removed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Vector2 newVelocity = body.LinearVelocity;
|
Vector2 newVelocity = body.LinearVelocity;
|
||||||
Vector2 newPosition = body.SimPosition;
|
Vector2 newPosition = body.SimPosition;
|
||||||
float newAngularVelocity = body.AngularVelocity;
|
float newAngularVelocity = body.AngularVelocity;
|
||||||
|
|||||||
@@ -202,6 +202,12 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private void ConnectToServer(string hostIP)
|
private void ConnectToServer(string hostIP)
|
||||||
{
|
{
|
||||||
|
chatBox.InputBox.Enabled = false;
|
||||||
|
if (GameMain.NetLobbyScreen?.TextBox != null)
|
||||||
|
{
|
||||||
|
GameMain.NetLobbyScreen.TextBox.Enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
string[] address = hostIP.Split(':');
|
string[] address = hostIP.Split(':');
|
||||||
if (address.Length == 1)
|
if (address.Length == 1)
|
||||||
{
|
{
|
||||||
@@ -259,7 +265,11 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
DebugConsole.ThrowError("Couldn't connect to " + hostIP + ". Error message: " + e.Message);
|
DebugConsole.ThrowError("Couldn't connect to " + hostIP + ". Error message: " + e.Message);
|
||||||
Disconnect();
|
Disconnect();
|
||||||
|
chatBox.InputBox.Enabled = true;
|
||||||
|
if (GameMain.NetLobbyScreen?.TextBox != null)
|
||||||
|
{
|
||||||
|
GameMain.NetLobbyScreen.TextBox.Enabled = true;
|
||||||
|
}
|
||||||
GameMain.ServerListScreen.Select();
|
GameMain.ServerListScreen.Select();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -530,6 +540,10 @@ namespace Barotrauma.Networking
|
|||||||
GameMain.NetLobbyScreen.Select();
|
GameMain.NetLobbyScreen.Select();
|
||||||
}
|
}
|
||||||
connected = true;
|
connected = true;
|
||||||
|
if (GameMain.NetLobbyScreen?.TextBox != null)
|
||||||
|
{
|
||||||
|
GameMain.NetLobbyScreen.TextBox.Enabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return CoroutineStatus.Success;
|
yield return CoroutineStatus.Success;
|
||||||
|
|||||||
@@ -118,7 +118,18 @@ namespace Barotrauma
|
|||||||
CanBeFocused = false
|
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
|
OnSelected = SelectCharacter
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -438,6 +438,43 @@ namespace Barotrauma
|
|||||||
UpdateSourceRect(limb, newRect);
|
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)
|
if (!isFreezed)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,9 +38,18 @@ namespace Barotrauma
|
|||||||
#region Creation
|
#region Creation
|
||||||
public MainMenuScreen(GameMain game)
|
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)
|
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)
|
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 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,
|
Stretch = false,
|
||||||
RelativeSpacing = 0.035f
|
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 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,
|
Stretch = false,
|
||||||
RelativeSpacing = 0.035f
|
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 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,
|
Stretch = false,
|
||||||
RelativeSpacing = 0.035f
|
RelativeSpacing = 0.035f
|
||||||
@@ -673,7 +682,12 @@ namespace Barotrauma
|
|||||||
backgroundSprite = (LocationType.List.Where(l => l.UseInMainMenu).GetRandom()).GetPortrait(0);
|
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)
|
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
|
||||||
|
|||||||
@@ -625,8 +625,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (Item item in itemInventory.Items)
|
foreach (Item item in itemInventory.Items)
|
||||||
{
|
{
|
||||||
if (item == null) continue;
|
item?.Drop(null);
|
||||||
item.Drop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // If current screen is not subeditor, delete anyway to avoid lingering objects
|
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)
|
foreach (Item item in itemInventory.Items)
|
||||||
{
|
{
|
||||||
if (item == null) continue;
|
item?.Drop(null);
|
||||||
item.Drop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // If current screen is not subeditor, delete anyway to avoid lingering objects
|
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)
|
foreach (Item item in itemInventory.Items)
|
||||||
{
|
{
|
||||||
if (item == null) continue;
|
item?.Drop(null);
|
||||||
item.Drop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else // If current screen is not subeditor, delete anyway to avoid lingering objects
|
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);
|
Item existingWire = dummyCharacter.SelectedItems.FirstOrDefault(i => i != null && i.Prefab == userData as ItemPrefab);
|
||||||
if (existingWire != null)
|
if (existingWire != null)
|
||||||
{
|
{
|
||||||
existingWire.Drop();
|
existingWire.Drop(null);
|
||||||
existingWire.Remove();
|
existingWire.Remove();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1470,7 +1467,7 @@ namespace Barotrauma
|
|||||||
existingWire = dummyCharacter.Inventory.Items[slotIndex];
|
existingWire = dummyCharacter.Inventory.Items[slotIndex];
|
||||||
if (existingWire != null && existingWire.Prefab != userData as ItemPrefab)
|
if (existingWire != null && existingWire.Prefab != userData as ItemPrefab)
|
||||||
{
|
{
|
||||||
existingWire.Drop();
|
existingWire.Drop(null);
|
||||||
existingWire.Remove();
|
existingWire.Remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,12 @@ namespace Barotrauma.Sounds
|
|||||||
{
|
{
|
||||||
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
|
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
|
||||||
}
|
}
|
||||||
|
public int UniqueLoadedSoundCount
|
||||||
|
{
|
||||||
|
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, Pair<float, bool>> categoryModifiers;
|
||||||
|
|
||||||
private Dictionary<string, Pair<float, bool>> categoryModifiers;
|
private Dictionary<string, Pair<float, bool>> categoryModifiers;
|
||||||
|
|
||||||
@@ -418,6 +424,10 @@ namespace Barotrauma.Sounds
|
|||||||
{
|
{
|
||||||
categoryModifiers[category].Second = muffle;
|
categoryModifiers[category].Second = muffle;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
categoryModifiers[category].Second = muffle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < playingChannels.Length; i++)
|
for (int i = 0; i < playingChannels.Length; i++)
|
||||||
|
|||||||
@@ -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<int> conversationClosingIndent = new List<int>();
|
||||||
|
private static char[] separator = new char[1] { ',' };
|
||||||
|
|
||||||
|
private const string conversationsPath = "Content/NPCConversations";
|
||||||
|
private const string infoTextPath = "Content/Texts";
|
||||||
|
private const string xmlHeader = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
|
||||||
|
|
||||||
|
public static void Convert(string language)
|
||||||
|
{
|
||||||
|
List<string> conversationFiles = new List<string>();
|
||||||
|
List<string> infoTextFiles = new List<string>();
|
||||||
|
|
||||||
|
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<string> 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<string> 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<string> ConvertInfoTextToXML(string[] csvContent, string language)
|
||||||
|
{
|
||||||
|
List<string> xmlContent = new List<string>();
|
||||||
|
xmlContent.Add(xmlHeader);
|
||||||
|
|
||||||
|
xmlContent.Add($"<infotexts language=\"{language}\">");
|
||||||
|
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]}><!-- No data --></{split[0]}>");
|
||||||
|
}
|
||||||
|
else // A header
|
||||||
|
{
|
||||||
|
xmlContent.Add($"<!-- {split[0]} -->");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlContent.Add(string.Empty);
|
||||||
|
xmlContent.Add("</infotexts>");
|
||||||
|
|
||||||
|
return xmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<string> ConvertConversationsToXML(string[] csvContent, string language)
|
||||||
|
{
|
||||||
|
List<string> xmlContent = new List<string>();
|
||||||
|
xmlContent.Add(xmlHeader);
|
||||||
|
|
||||||
|
xmlContent.Add($"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\">");
|
||||||
|
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($"<!-- {split[0]} -->");
|
||||||
|
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)}" +
|
||||||
|
$"<Conversation line=\"{line}\" " +
|
||||||
|
$"{GetVariable("speaker" ,speaker)}" +
|
||||||
|
$"{GetVariable("flags", flags)}" +
|
||||||
|
$"{GetVariable("allowedjobs", allowedJobs)}" +
|
||||||
|
$"{GetVariable("speakertags", speakerTags)}" +
|
||||||
|
$"{GetVariable("minintensity", minIntensity)}" +
|
||||||
|
$"{GetVariable("maxintensity", maxIntensity)}";
|
||||||
|
|
||||||
|
bool nextIsSubConvo = false;
|
||||||
|
int nextDepth = 999;
|
||||||
|
|
||||||
|
if (i < csvContent.Length - 1) // Not at the end of file
|
||||||
|
{
|
||||||
|
string[] nextConversationElement = csvContent[i + 1].Split(separator);
|
||||||
|
|
||||||
|
if (nextConversationElement[1] != string.Empty)
|
||||||
|
{
|
||||||
|
nextDepth = int.Parse(nextConversationElement[2]);
|
||||||
|
nextIsSubConvo = nextDepth > 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("</Conversations>");
|
||||||
|
|
||||||
|
return xmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void HandleClosingElements(List<string> 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)}</Conversation>");
|
||||||
|
conversationClosingIndent.RemoveAt(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] SplitCSV(string input) // Splits the .csv with regex, leaving commas inside quotation marks intact
|
||||||
|
{
|
||||||
|
List<string> list = new List<string>();
|
||||||
|
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
|
||||||
@@ -169,6 +169,12 @@
|
|||||||
<Content Include="plugins\video_output\libvmem_plugin.dll">
|
<Content Include="plugins\video_output\libvmem_plugin.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Include="Shaders\blurshader.fx" />
|
||||||
|
<Content Include="Shaders\damageshader.fx" />
|
||||||
|
<Content Include="Shaders\deformshader.fx" />
|
||||||
|
<Content Include="Shaders\losshader.fx" />
|
||||||
|
<Content Include="Shaders\postprocess.fx" />
|
||||||
|
<Content Include="Shaders\watershader.fx" />
|
||||||
<Content Include="steam_api64.dll">
|
<Content Include="steam_api64.dll">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -250,6 +256,7 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Include="packages.config" />
|
<None Include="packages.config" />
|
||||||
|
<None Include="Shaders\Content.mgcb" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup />
|
<ItemGroup />
|
||||||
<Import Project="ClientCode.projitems" Label="Shared" />
|
<Import Project="ClientCode.projitems" Label="Shared" />
|
||||||
|
|||||||
@@ -167,10 +167,7 @@ namespace Barotrauma
|
|||||||
c.KickAFKTimer = 0.0f;
|
c.KickAFKTimer = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newInput.HasFlag(InputNetFlags.Aim))
|
newAim = msg.ReadUInt16();
|
||||||
{
|
|
||||||
newAim = msg.ReadUInt16();
|
|
||||||
}
|
|
||||||
if (newInput.HasFlag(InputNetFlags.Select) ||
|
if (newInput.HasFlag(InputNetFlags.Select) ||
|
||||||
newInput.HasFlag(InputNetFlags.Use) ||
|
newInput.HasFlag(InputNetFlags.Use) ||
|
||||||
newInput.HasFlag(InputNetFlags.Health) ||
|
newInput.HasFlag(InputNetFlags.Health) ||
|
||||||
@@ -181,12 +178,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (NetIdUtils.IdMoreRecent((ushort)(networkUpdateID - i), LastNetworkUpdateID) && (i < 60))
|
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
|
NetInputMem newMem = new NetInputMem
|
||||||
{
|
{
|
||||||
states = newInput,
|
states = newInput,
|
||||||
intAim = newAim,
|
intAim = newAim,
|
||||||
interact = newInteract,
|
interact = newInteract,
|
||||||
|
|
||||||
networkUpdateID = (ushort)(networkUpdateID - i)
|
networkUpdateID = (ushort)(networkUpdateID - i)
|
||||||
};
|
};
|
||||||
memInput.Insert(i, newMem);
|
memInput.Insert(i, newMem);
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (newItemIDs[i] == 0 || (newItem != Items[i]))
|
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);
|
System.Diagnostics.Debug.Assert(Items[i] == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -355,8 +355,10 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
entityEventManager.Update(connectedClients);
|
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;
|
if (character.IsDead || !character.ClientDisconnected) continue;
|
||||||
|
|
||||||
character.KillDisconnectedTimer += deltaTime;
|
character.KillDisconnectedTimer += deltaTime;
|
||||||
@@ -1957,8 +1959,8 @@ namespace Barotrauma.Networking
|
|||||||
if (client == null) return;
|
if (client == null) return;
|
||||||
if (client.Connection == OwnerConnection) return;
|
if (client.Connection == OwnerConnection) return;
|
||||||
|
|
||||||
string msg = DisconnectReason.Banned.ToString();
|
string targetMsg = DisconnectReason.Banned.ToString();
|
||||||
DisconnectClient(client, $"ServerMessage.BannedFromServer~[client]={client.Name}", msg, reason);
|
DisconnectClient(client, $"ServerMessage.BannedFromServer~[client]={client.Name}", targetMsg, reason);
|
||||||
|
|
||||||
if (client.SteamID == 0 || range)
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the message to the chatbox and pass it to all clients who can receive it
|
||||||
|
/// </summary>
|
||||||
|
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<WifiComponent>() != null);
|
||||||
|
if (radio == null || !senderCharacter.HasEquippedItem(radio)) return;
|
||||||
|
|
||||||
|
senderRadio = radio.GetComponent<WifiComponent>();
|
||||||
|
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<Client> 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<NetConnection> recipients = new List<NetConnection>() { 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<JobPrefab> jobPreferences = new List<JobPrefab>();
|
||||||
|
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<Client> unassigned)
|
||||||
|
{
|
||||||
|
unassigned = new List<Client>(unassigned);
|
||||||
|
|
||||||
|
Dictionary<JobPrefab, int> assignedClientCount = new Dictionary<JobPrefab, int>();
|
||||||
|
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<Client, Job> 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<CharacterInfo> bots, Character.TeamType teamID)
|
||||||
|
{
|
||||||
|
Dictionary<JobPrefab, int> assignedPlayerCount = new Dictionary<JobPrefab, int>();
|
||||||
|
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<CharacterInfo> unassignedBots = new List<CharacterInfo>(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<Client> 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()
|
public override void Disconnect()
|
||||||
{
|
{
|
||||||
serverSettings.BanList.Save();
|
serverSettings.BanList.Save();
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Barotrauma.Networking
|
|||||||
if (GameMain.Config.RequireSteamAuthentication)
|
if (GameMain.Config.RequireSteamAuthentication)
|
||||||
{
|
{
|
||||||
Log("Disconnected unauthenticated client (Steam ID: " + steamID + "). Steam authentication failed, (" + status + ").", ServerLog.MessageType.ServerMessage);
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -500,7 +500,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message)
|
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.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
|
||||||
if (unauthClient != null)
|
if (unauthClient != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1029,25 +1029,34 @@
|
|||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LargeInsideWallsBack.png">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LargeInsideWallsBack.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\AlienRuins.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\AlienRuins.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\AlienRuins2.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\Colony.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\Colony.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\HabitationOutpost.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\EmptyLocation.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\NaturalFormations.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\NaturalFormations.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMilitary.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMining1.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMining1.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMining2.jpg">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMining2.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMining3.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampResearchFacility1.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampResearchFacility2.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapIconAtlas.png">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapIconAtlas.png">
|
||||||
@@ -1298,15 +1307,6 @@
|
|||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapGenerationParameters.xml">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapGenerationParameters.xml">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\HabitationOutpost.jpg">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampMilitary.jpg">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\LocationPortraits\WorkCampResearchFacility.jpg">
|
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
|
||||||
</Content>
|
|
||||||
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapHUD.png">
|
<Content Include="$(MSBuildThisFileDirectory)Content\Map\MapHUD.png">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -3108,5 +3108,8 @@
|
|||||||
<None Include="$(MSBuildThisFileDirectory)Content\Items\Alien\AlienDoor.ogg">
|
<None Include="$(MSBuildThisFileDirectory)Content\Items\Alien\AlienDoor.ogg">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Include="$(MSBuildThisFileDirectory)Submarines\Venture.sub">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -22,7 +22,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public const float HULL_SAFETY_THRESHOLD = 50;
|
public const float HULL_SAFETY_THRESHOLD = 50;
|
||||||
|
|
||||||
// TODO: update the list when someone gives a report
|
|
||||||
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
public HashSet<Hull> UnsafeHulls { get; private set; } = new HashSet<Hull>();
|
||||||
|
|
||||||
private SteeringManager outsideSteering, insideSteering;
|
private SteeringManager outsideSteering, insideSteering;
|
||||||
@@ -136,10 +135,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
Character.AnimController.IgnorePlatforms = ignorePlatforms;
|
||||||
|
|
||||||
if (Character.IsClimbing)
|
// 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));
|
//{
|
||||||
}
|
// Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
|
||||||
|
//}
|
||||||
|
|
||||||
Vector2 targetMovement = AnimController.TargetMovement;
|
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
|
// 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>() { InvSlotType.Any }))
|
if (!mask.AllowedSlots.Contains(InvSlotType.Any) || !Character.Inventory.TryPutItem(mask, Character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||||
{
|
{
|
||||||
mask.Drop();
|
mask.Drop(Character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,7 +198,7 @@ namespace Barotrauma
|
|||||||
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
|
if (extinguisherItem != null && Character.HasEquippedItem(extinguisherItem))
|
||||||
{
|
{
|
||||||
// TODO: take the item where it was taken from?
|
// TODO: take the item where it was taken from?
|
||||||
extinguisherItem.Drop();
|
extinguisherItem.Drop(Character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +228,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReportProblems()
|
||||||
|
{
|
||||||
|
if (GameMain.Client != null) return;
|
||||||
|
|
||||||
protected void ReportProblems()
|
protected void ReportProblems()
|
||||||
{
|
{
|
||||||
Order newOrder = null;
|
Order newOrder = null;
|
||||||
@@ -272,10 +276,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReportProblems()
|
|
||||||
{
|
|
||||||
if (GameMain.Client != null) return;
|
|
||||||
|
|
||||||
private void UpdateSpeaking()
|
private void UpdateSpeaking()
|
||||||
{
|
{
|
||||||
if (Character.Oxygen < 20.0f)
|
if (Character.Oxygen < 20.0f)
|
||||||
@@ -296,38 +296,65 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
public override void OnAttacked(Character attacker, AttackResult attackResult)
|
||||||
{
|
{
|
||||||
float totalDamage = attackResult.Damage;
|
float damage = attackResult.Damage;
|
||||||
if (totalDamage <= 0.0f || attacker == null) return;
|
if (damage < 0) { return; }
|
||||||
|
if (attacker == null || attacker.IsDead || attacker.Removed)
|
||||||
if (attacker.SpeciesName == Character.SpeciesName)
|
{
|
||||||
|
objectiveManager.GetObjective<AIObjectiveFindSafety>().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)
|
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.
|
// 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).
|
// Should not cancel any existing ai objectives (so that if the character attacked you and then helped, we still would want to retaliate).
|
||||||
return;
|
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<AIObjectiveFindSafety>().Priority = 100;
|
||||||
|
return;
|
||||||
|
}
|
||||||
float currentVitality = Character.CharacterHealth.Vitality;
|
float currentVitality = Character.CharacterHealth.Vitality;
|
||||||
float dmgPercentage = totalDamage / currentVitality * 100;
|
float dmgPercentage = damage / currentVitality * 100;
|
||||||
if (dmgPercentage < currentVitality / 10)
|
if (dmgPercentage < currentVitality / 10)
|
||||||
{
|
{
|
||||||
// Don't react to a minor amount of (accidental) dmg done by friendly characters
|
// Don't react to a minor amount of (accidental) dmg done by friendly characters
|
||||||
|
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
|
||||||
return;
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
objectiveManager.AddObjective(new AIObjectiveCombat(Character, attacker), Rand.Range(0.5f, 1, Rand.RandSync.Unsynced), () =>
|
|
||||||
{
|
{
|
||||||
//the objective in the manager is not necessarily the same as the one we just instantiated,
|
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
|
||||||
//because the objective isn't added if there's already an identical objective in the manager
|
{
|
||||||
var combatObjective = objectiveManager.GetObjective<AIObjectiveCombat>();
|
if (combatObjective.Enemy != attacker)
|
||||||
combatObjective.MaxEnemyDamage = Math.Max(totalDamage, combatObjective.MaxEnemyDamage);
|
{
|
||||||
});
|
// 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)
|
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 ignoreFire = ObjectiveManager.CurrentObjective is AIObjectiveExtinguishFire || ObjectiveManager.CurrentOrder is AIObjectiveExtinguishFires;
|
||||||
bool ignoreWater = HasDivingSuit(Character);
|
bool ignoreWater = HasDivingSuit(Character);
|
||||||
bool ignoreOxygen = ignoreWater || HasDivingGear(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)
|
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;
|
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
|
||||||
return MathHelper.Clamp(safety * 100, 0, 100);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ namespace Barotrauma
|
|||||||
public virtual bool KeepDivingGearOn => false;
|
public virtual bool KeepDivingGearOn => false;
|
||||||
|
|
||||||
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
protected readonly List<AIObjective> subObjectives = new List<AIObjective>();
|
||||||
protected float priority;
|
public float Priority { get; set; }
|
||||||
protected readonly Character character;
|
protected readonly Character character;
|
||||||
protected string option;
|
protected string option;
|
||||||
protected bool abandon;
|
protected bool abandon;
|
||||||
@@ -26,6 +26,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
|
protected HumanAIController HumanAIController => character.AIController as HumanAIController;
|
||||||
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
|
protected IndoorsSteeringManager PathSteering => HumanAIController.PathSteering;
|
||||||
|
protected SteeringManager SteeringManager => HumanAIController.SteeringManager;
|
||||||
|
|
||||||
public string Option
|
public string Option
|
||||||
{
|
{
|
||||||
@@ -103,20 +104,20 @@ namespace Barotrauma
|
|||||||
CurrentSubObjective.SortSubObjectives(objectiveManager);
|
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)
|
public virtual void Update(AIObjectiveManager objectiveManager, float deltaTime)
|
||||||
{
|
{
|
||||||
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
|
var subObjective = objectiveManager.CurrentObjective?.CurrentSubObjective;
|
||||||
if (objectiveManager.CurrentOrder == this)
|
if (objectiveManager.CurrentOrder == this)
|
||||||
{
|
{
|
||||||
priority = AIObjectiveManager.OrderPriority;
|
Priority = AIObjectiveManager.OrderPriority;
|
||||||
}
|
}
|
||||||
else if (objectiveManager.CurrentObjective == this || subObjective == this)
|
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));
|
subObjectives.ForEach(so => so.Update(objectiveManager, deltaTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+205
-131
@@ -1,194 +1,268 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class AIObjectiveCombat : AIObjective
|
class AIObjectiveCombat : AIObjective
|
||||||
{
|
{
|
||||||
public override string DebugTag => "combat";
|
public override string DebugTag => "combat";
|
||||||
public override bool ForceRun => true;
|
|
||||||
public override bool KeepDivingGearOn => true;
|
public override bool KeepDivingGearOn => true;
|
||||||
|
|
||||||
const float CoolDown = 10.0f;
|
const float CoolDown = 10.0f;
|
||||||
|
|
||||||
//the largest amount of damage the enemy has inflicted on this character
|
public Character Enemy { get; private set; }
|
||||||
//(may be higher than enemyStrength if the enemy is e.g. a human using items)
|
|
||||||
public float MaxEnemyDamage;
|
|
||||||
|
|
||||||
private Character enemy;
|
|
||||||
|
|
||||||
private AIObjectiveFindSafety escapeObjective;
|
|
||||||
|
|
||||||
|
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<RangedWeapon>() as ItemComponent ??
|
||||||
|
Weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
|
||||||
|
Weapon.GetComponent<RepairTool>() as ItemComponent;
|
||||||
|
}
|
||||||
|
return _weaponComponent;
|
||||||
|
}
|
||||||
|
}
|
||||||
private AIObjectiveContainItem reloadWeaponObjective;
|
private AIObjectiveContainItem reloadWeaponObjective;
|
||||||
|
private Hull retreatTarget;
|
||||||
|
private AIObjectiveGoTo retreatObjective;
|
||||||
|
|
||||||
private float coolDownTimer;
|
private float coolDownTimer;
|
||||||
|
|
||||||
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
|
public AIObjectiveCombat(Character character, Character enemy) : base(character, "")
|
||||||
{
|
{
|
||||||
this.enemy = enemy;
|
Enemy = enemy;
|
||||||
coolDownTimer = CoolDown;
|
coolDownTimer = CoolDown;
|
||||||
|
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
coolDownTimer -= deltaTime;
|
coolDownTimer -= deltaTime;
|
||||||
|
if (Weapon != null && character.Inventory.Items.Contains(_weapon))
|
||||||
var weapon = character.Inventory.FindItemByTag("weapon");
|
{
|
||||||
if (weapon == null)
|
Weapon = null;
|
||||||
|
}
|
||||||
|
if (Weapon == null)
|
||||||
|
{
|
||||||
|
Weapon = GetWeapon();
|
||||||
|
}
|
||||||
|
if (Weapon == null)
|
||||||
{
|
{
|
||||||
Escape(deltaTime);
|
Escape(deltaTime);
|
||||||
}
|
}
|
||||||
else
|
else if (Equip(deltaTime))
|
||||||
{
|
{
|
||||||
if (!character.SelectedItems.Contains(weapon))
|
if (Reload(deltaTime))
|
||||||
{
|
{
|
||||||
if (character.Inventory.TryPutItem(weapon, 3, true, false, character))
|
Attack(deltaTime);
|
||||||
{
|
|
||||||
weapon.Equip(character);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//couldn't equip the item, escape
|
|
||||||
Escape(deltaTime);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (!abandon)
|
||||||
|
{
|
||||||
|
Move(deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//make sure the weapon is loaded
|
private Item GetWeapon()
|
||||||
var weaponComponent =
|
{
|
||||||
weapon.GetComponent<RangedWeapon>() as ItemComponent ??
|
_weaponComponent = null;
|
||||||
weapon.GetComponent<MeleeWeapon>() as ItemComponent ??
|
var weapon = character.Inventory.FindItemByTag("weapon");
|
||||||
weapon.GetComponent<RepairTool>() as ItemComponent;
|
if (weapon == null)
|
||||||
if (weaponComponent != null && weaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
{
|
||||||
|
foreach (var item in character.Inventory.Items)
|
||||||
{
|
{
|
||||||
var containedItems = weapon.ContainedItems;
|
if (item == null) { continue; }
|
||||||
foreach (RelatedItem requiredItem in weaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
foreach (var component in item.Components)
|
||||||
{
|
{
|
||||||
Item containedItem = containedItems.FirstOrDefault(it => it.Condition > 0.0f && requiredItem.MatchesItem(it));
|
if (component is MeleeWeapon || component is RangedWeapon)
|
||||||
if (containedItem == null)
|
|
||||||
{
|
{
|
||||||
var newReloadWeaponObjective = new AIObjectiveContainItem(character, requiredItem.Identifiers, weapon.GetComponent<ItemContainer>());
|
return item;
|
||||||
if (!newReloadWeaponObjective.IsDuplicate(reloadWeaponObjective))
|
}
|
||||||
|
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())
|
Weapon.Equip(character);
|
||||||
{
|
|
||||||
reloadWeaponObjective = null;
|
|
||||||
}
|
|
||||||
else if (!reloadWeaponObjective.CanBeCompleted)
|
|
||||||
{
|
|
||||||
Escape(deltaTime);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
reloadWeaponObjective.TryComplete(deltaTime);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
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<FarseerPhysics.Dynamics.Body> ignoredBodies = new List<FarseerPhysics.Dynamics.Body>();
|
|
||||||
foreach (Limb limb in character.AnimController.Limbs)
|
|
||||||
{
|
{
|
||||||
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);
|
private void Move(float deltaTime)
|
||||||
if (pickedBody != null && !(pickedBody.UserData is Limb)) return;
|
{
|
||||||
|
// Retreat to safety
|
||||||
|
// TODO: aggressive behaviour, chasing?
|
||||||
|
if (retreatTarget == null || (retreatObjective != null && !retreatObjective.CanBeCompleted))
|
||||||
|
{
|
||||||
|
retreatTarget = HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().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<ItemContainer>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (reloadWeaponObjective != null)
|
||||||
|
{
|
||||||
|
if (reloadWeaponObjective.IsCompleted())
|
||||||
|
{
|
||||||
|
reloadWeaponObjective = null;
|
||||||
|
}
|
||||||
|
else if (!reloadWeaponObjective.CanBeCompleted)
|
||||||
|
{
|
||||||
|
Escape(deltaTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reloadWeaponObjective.TryComplete(deltaTime);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<FarseerPhysics.Dynamics.Body> 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)
|
private void Escape(float deltaTime)
|
||||||
{
|
{
|
||||||
// TODO: just let the find safety run?
|
abandon = true;
|
||||||
if (escapeObjective == null)
|
SteeringManager.Reset();
|
||||||
{
|
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
|
||||||
escapeObjective = new AIObjectiveFindSafety(character);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enemy.AnimController.CurrentHull == character.AnimController.CurrentHull)
|
|
||||||
{
|
|
||||||
escapeObjective.OverrideCurrentHullSafety = 0.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
escapeObjective.OverrideCurrentHullSafety = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
escapeObjective.TryComplete(deltaTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsCompleted()
|
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);
|
||||||
return enemy == null || enemy.Removed || enemy.IsDead || coolDownTimer <= 0.0f;
|
public override float GetPriority(AIObjectiveManager objectiveManager) => Enemy == null || Enemy.Removed || Enemy.IsDead ? 0 : 100;
|
||||||
}
|
|
||||||
|
|
||||||
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 IsDuplicate(AIObjective otherObjective)
|
public override bool IsDuplicate(AIObjective otherObjective)
|
||||||
{
|
{
|
||||||
AIObjectiveCombat objective = otherObjective as AIObjectiveCombat;
|
if (!(otherObjective is AIObjectiveCombat objective)) return false;
|
||||||
if (objective == null) return false;
|
return objective.Enemy == Enemy;
|
||||||
|
|
||||||
return objective.enemy == enemy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float CalculateEnemyStrength()
|
//private float CalculateEnemyStrength()
|
||||||
{
|
//{
|
||||||
float enemyStrength = 0;
|
// float enemyStrength = 0;
|
||||||
AttackContext currentContext = character.GetAttackContext();
|
// AttackContext currentContext = character.GetAttackContext();
|
||||||
foreach (Limb limb in enemy.AnimController.Limbs)
|
// foreach (Limb limb in Enemy.AnimController.Limbs)
|
||||||
{
|
// {
|
||||||
if (limb.attack == null) continue;
|
// if (limb.attack == null) continue;
|
||||||
if (!limb.attack.IsValidContext(currentContext)) { continue; }
|
// if (!limb.attack.IsValidContext(currentContext)) { continue; }
|
||||||
if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
|
// if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
|
||||||
enemyStrength += limb.attack.GetTotalDamage(false);
|
// enemyStrength += limb.attack.GetTotalDamage(false);
|
||||||
}
|
// }
|
||||||
return enemyStrength;
|
// return enemyStrength;
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -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 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 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));
|
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()
|
public override bool IsCompleted()
|
||||||
|
|||||||
+1
-1
@@ -62,7 +62,7 @@ namespace Barotrauma
|
|||||||
if (containedItem == null) continue;
|
if (containedItem == null) continue;
|
||||||
if (containedItem.Condition <= 0.0f)
|
if (containedItem.Condition <= 0.0f)
|
||||||
{
|
{
|
||||||
containedItem.Drop();
|
containedItem.Drop(character);
|
||||||
}
|
}
|
||||||
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
|
else if (containedItem.Prefab.Identifier == "oxygentank" || containedItem.HasTag("oxygensource"))
|
||||||
{
|
{
|
||||||
|
|||||||
+44
-35
@@ -16,18 +16,17 @@ namespace Barotrauma
|
|||||||
const float priorityIncrease = 25;
|
const float priorityIncrease = 25;
|
||||||
const float priorityDecrease = 10;
|
const float priorityDecrease = 10;
|
||||||
const float SearchHullInterval = 3.0f;
|
const float SearchHullInterval = 3.0f;
|
||||||
|
const float clearUnreachableInterval = 30;
|
||||||
|
|
||||||
private List<Hull> unreachable = new List<Hull>();
|
private List<Hull> unreachable = new List<Hull>();
|
||||||
|
|
||||||
private float currenthullSafety;
|
private float currenthullSafety;
|
||||||
|
private float unreachableClearTimer;
|
||||||
private float searchHullTimer;
|
private float searchHullTimer;
|
||||||
|
|
||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
private AIObjective divingGearObjective;
|
private AIObjective divingGearObjective;
|
||||||
|
|
||||||
public float? OverrideCurrentHullSafety;
|
|
||||||
|
|
||||||
public AIObjectiveFindSafety(Character character) : base(character, "") { }
|
public AIObjectiveFindSafety(Character character) : base(character, "") { }
|
||||||
|
|
||||||
public override bool IsCompleted() => false;
|
public override bool IsCompleted() => false;
|
||||||
@@ -52,7 +51,7 @@ namespace Barotrauma
|
|||||||
if (divingGearObjective.IsCompleted())
|
if (divingGearObjective.IsCompleted())
|
||||||
{
|
{
|
||||||
divingGearObjective = null;
|
divingGearObjective = null;
|
||||||
priority = 0;
|
Priority = 0;
|
||||||
}
|
}
|
||||||
else if (divingGearObjective.CanBeCompleted)
|
else if (divingGearObjective.CanBeCompleted)
|
||||||
{
|
{
|
||||||
@@ -61,6 +60,16 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (unreachableClearTimer > 0)
|
||||||
|
{
|
||||||
|
unreachableClearTimer -= deltaTime;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
unreachableClearTimer = clearUnreachableInterval;
|
||||||
|
unreachable.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
if (searchHullTimer > 0.0f)
|
if (searchHullTimer > 0.0f)
|
||||||
{
|
{
|
||||||
searchHullTimer -= deltaTime;
|
searchHullTimer -= deltaTime;
|
||||||
@@ -147,16 +156,35 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Hull FindBestHull()
|
public Hull FindBestHull()
|
||||||
{
|
{
|
||||||
Hull bestHull = character.CurrentHull;
|
Hull bestHull = character.CurrentHull;
|
||||||
float bestValue = currenthullSafety;
|
float bestValue = currenthullSafety;
|
||||||
foreach (Hull hull in Hull.hullList)
|
foreach (Hull hull in Hull.hullList)
|
||||||
{
|
{
|
||||||
if (unreachable.Contains(hull)) { continue; }
|
|
||||||
if (hull.Submarine == null) { continue; }
|
if (hull.Submarine == null) { continue; }
|
||||||
float hullSafety = 0;
|
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
|
// Outside
|
||||||
if (hull.RoomName?.ToLowerInvariant() == "airlock")
|
if (hull.RoomName?.ToLowerInvariant() == "airlock")
|
||||||
@@ -177,7 +205,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Huge preference for closer targets
|
// 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;
|
hullSafety *= distanceFactor;
|
||||||
// If the target is not inside a friendly submarine, considerably reduce the hull safety.
|
// 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)
|
if (hull.Submarine.TeamID != character.TeamID && hull.Submarine.TeamID != Character.TeamType.FriendlyNPC)
|
||||||
@@ -185,26 +214,6 @@ namespace Barotrauma
|
|||||||
hullSafety /= 10;
|
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)
|
if (hullSafety > bestValue)
|
||||||
{
|
{
|
||||||
bestHull = hull;
|
bestHull = hull;
|
||||||
@@ -224,24 +233,24 @@ namespace Barotrauma
|
|||||||
if (character.CurrentHull == null)
|
if (character.CurrentHull == null)
|
||||||
{
|
{
|
||||||
currenthullSafety = 0;
|
currenthullSafety = 0;
|
||||||
priority = 5;
|
Priority = 5;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { priority = 100; }
|
if (character.OxygenAvailable < CharacterHealth.LowOxygenThreshold) { Priority = 100; }
|
||||||
currenthullSafety = OverrideCurrentHullSafety ?? HumanAIController.GetHullSafety(character.CurrentHull);
|
currenthullSafety = HumanAIController.GetHullSafety(character.CurrentHull);
|
||||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||||
{
|
{
|
||||||
priority -= priorityDecrease * deltaTime;
|
Priority -= priorityDecrease * deltaTime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
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)
|
if (divingGearObjective != null && !divingGearObjective.IsCompleted() && divingGearObjective.CanBeCompleted)
|
||||||
{
|
{
|
||||||
priority = Math.Max(priority, AIObjectiveManager.OrderPriority + 10);
|
Priority = Math.Max(Priority, AIObjectiveManager.OrderPriority + 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ namespace Barotrauma
|
|||||||
public override string DebugTag => "fix leak";
|
public override string DebugTag => "fix leak";
|
||||||
|
|
||||||
public override bool KeepDivingGearOn => true;
|
public override bool KeepDivingGearOn => true;
|
||||||
|
public override bool ForceRun => true;
|
||||||
|
|
||||||
private readonly Gap leak;
|
private readonly Gap leak;
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public override string DebugTag => "fix leaks";
|
public override string DebugTag => "fix leaks";
|
||||||
public override bool KeepDivingGearOn => true;
|
public override bool KeepDivingGearOn => true;
|
||||||
|
public override bool ForceRun => true;
|
||||||
|
|
||||||
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
|
public AIObjectiveFixLeaks(Character character) : base (character, "") { }
|
||||||
|
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ namespace Barotrauma
|
|||||||
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) continue;
|
if (character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any })) continue;
|
||||||
|
|
||||||
//if everything else fails, simply drop the existing item
|
//if everything else fails, simply drop the existing item
|
||||||
character.Inventory.Items[i].Drop();
|
character.Inventory.Items[i].Drop(character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,17 +45,22 @@ namespace Barotrauma
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
bool canComplete = !cannotReach && !abandon;
|
bool canComplete = !cannotReach && !abandon;
|
||||||
if (FollowControlledCharacter && Character.Controlled == null) { canComplete = false; }
|
if (canComplete)
|
||||||
else if (Target != null && Target.Removed) { canComplete = false; }
|
|
||||||
else if (repeat || waitUntilPathUnreachable > 0.0f) { canComplete = true; }
|
|
||||||
else if (character.AIController.SteeringManager is IndoorsSteeringManager pathSteering)
|
|
||||||
{
|
{
|
||||||
//path doesn't exist (= hasn't been searched for yet), assume for now that the target is reachable TODO: add a timer?
|
if (FollowControlledCharacter && Character.Controlled == null)
|
||||||
if (pathSteering.CurrentPath == null) { canComplete = true; }
|
|
||||||
else if (!AllowGoingOutside && pathSteering.CurrentPath.HasOutdoorsNodes) { canComplete = false; }
|
|
||||||
if (canComplete)
|
|
||||||
{
|
{
|
||||||
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)
|
if (!canComplete)
|
||||||
@@ -144,9 +149,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var indoorSteering = character.AIController.SteeringManager as IndoorsSteeringManager;
|
bool targetIsOutside = (Target != null && Target.Submarine == null) ||
|
||||||
bool targetIsOutside = (Target != null && Target.Submarine == null) || (indoorSteering != null && indoorSteering.CurrentPath != null && indoorSteering.CurrentPath.HasOutdoorsNodes);
|
(SteeringManager == PathSteering && PathSteering.CurrentPath != null && PathSteering.CurrentPath.HasOutdoorsNodes);
|
||||||
if (targetIsOutside && !AllowGoingOutside)
|
if (targetIsOutside && character.CurrentHull != null && !AllowGoingOutside)
|
||||||
{
|
{
|
||||||
cannotReach = true;
|
cannotReach = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ namespace Barotrauma
|
|||||||
public override string DebugTag => "idle";
|
public override string DebugTag => "idle";
|
||||||
|
|
||||||
const float WallAvoidDistance = 150.0f;
|
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 Hull currentTarget;
|
||||||
private float newTargetTimer;
|
private float newTargetTimer;
|
||||||
@@ -46,7 +52,10 @@ namespace Barotrauma
|
|||||||
character.SelectedConstruction = null;
|
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;
|
newTargetTimer = 0;
|
||||||
standStillTimer = 0;
|
standStillTimer = 0;
|
||||||
@@ -70,7 +79,7 @@ namespace Barotrauma
|
|||||||
PathSteering.SetPath(path);
|
PathSteering.SetPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
newTargetTimer = currentTarget == null ? 5.0f : 15.0f;
|
newTargetTimer = currentTarget != null && character.AnimController.InWater ? newTargetIntervalMin : Rand.Range(newTargetIntervalMin, newTargetIntervalMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
newTargetTimer -= deltaTime;
|
newTargetTimer -= deltaTime;
|
||||||
@@ -79,20 +88,20 @@ namespace Barotrauma
|
|||||||
// - if reached the end of the path
|
// - if reached the end of the path
|
||||||
// - if the target is unreachable
|
// - if the target is unreachable
|
||||||
// - if the path requires going outside
|
// - 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)))
|
(PathSteering.CurrentPath.NextNode == null || PathSteering.CurrentPath.Unreachable || PathSteering.CurrentPath.HasOutdoorsNodes)))
|
||||||
{
|
{
|
||||||
standStillTimer -= deltaTime;
|
standStillTimer -= deltaTime;
|
||||||
if (standStillTimer > 0.0f)
|
if (standStillTimer > 0.0f)
|
||||||
{
|
{
|
||||||
walkDuration = Rand.Range(1.0f, 5.0f);
|
walkDuration = Rand.Range(walkDurationMin, walkDurationMax);
|
||||||
PathSteering.Reset();
|
PathSteering.Reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (standStillTimer < -walkDuration)
|
if (standStillTimer < -walkDuration)
|
||||||
{
|
{
|
||||||
standStillTimer = Rand.Range(1.0f, 10.0f);
|
standStillTimer = Rand.Range(standStillMin, standStillMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
//steer away from edges of the hull
|
//steer away from edges of the hull
|
||||||
@@ -128,12 +137,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
character.AIController.SteeringManager.SteeringWander();
|
character.AIController.SteeringManager.SteeringWander();
|
||||||
if (!character.IsClimbing)
|
if (!character.IsClimbing && !character.AnimController.InWater)
|
||||||
{
|
{
|
||||||
//reset vertical steering to prevent dropping down from platforms etc
|
//reset vertical steering to prevent dropping down from platforms etc
|
||||||
character.AIController.SteeringManager.ResetY();
|
character.AIController.SteeringManager.ResetY();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +158,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var idCard = character.Inventory.FindItemByIdentifier("idcard");
|
var idCard = character.Inventory.FindItemByIdentifier("idcard");
|
||||||
Hull targetHull = null;
|
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
|
//random chance of navigating back to the room where the character spawned
|
||||||
if (Rand.Int(5) == 1 && idCard != null)
|
if (Rand.Int(5) == 1 && idCard != null)
|
||||||
{
|
{
|
||||||
@@ -186,9 +195,13 @@ namespace Barotrauma
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check that there is no unsafe or forbidden hulls on the way to the target
|
if (isCurrentHullOK)
|
||||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
|
{
|
||||||
if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
|
// 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 we want to do a steering check, we should do it here, before setting the path
|
||||||
//if (path.Cost > 1000.0f) { continue; }
|
//if (path.Cost > 1000.0f) { continue; }
|
||||||
|
|||||||
@@ -56,23 +56,6 @@ namespace Barotrauma
|
|||||||
DelayedObjectives.Add(objective, coroutine);
|
DelayedObjectives.Add(objective, coroutine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Dictionary<AIObjective, CoroutineHandle> DelayedObjectives { get; private set; } = new Dictionary<AIObjective, CoroutineHandle>();
|
|
||||||
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<T>() where T : AIObjective
|
public T GetObjective<T>() where T : AIObjective
|
||||||
{
|
{
|
||||||
foreach (AIObjective objective in Objectives)
|
foreach (AIObjective objective in Objectives)
|
||||||
@@ -200,27 +183,6 @@ namespace Barotrauma
|
|||||||
if (order.TargetItemComponent == null) return;
|
if (order.TargetItemComponent == null) return;
|
||||||
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||||
break;
|
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<Steering>();
|
|
||||||
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:
|
default:
|
||||||
if (order.TargetItemComponent == null) return;
|
if (order.TargetItemComponent == null) return;
|
||||||
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
CurrentOrder = new AIObjectiveOperateItem(order.TargetItemComponent, character, option, false, null, order.UseController);
|
||||||
|
|||||||
+1
-1
@@ -135,7 +135,7 @@ namespace Barotrauma
|
|||||||
if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) ||
|
if (!character.Inventory.Items[i].AllowedSlots.Contains(InvSlotType.Any) ||
|
||||||
!character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any }))
|
!character.Inventory.TryPutItem(character.Inventory.Items[i], character, new List<InvSlotType>() { InvSlotType.Any }))
|
||||||
{
|
{
|
||||||
character.Inventory.Items[i].Drop();
|
character.Inventory.Items[i].Drop(character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (character.Inventory.TryPutItem(component.Item, i, true, false, character))
|
if (character.Inventory.TryPutItem(component.Item, i, true, false, character))
|
||||||
|
|||||||
+30
-1
@@ -35,7 +35,7 @@ namespace Barotrauma
|
|||||||
float damagePriority = MathHelper.Lerp(1, 0, (Item.Condition + 10) / Item.MaxCondition);
|
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 successFactor = MathHelper.Lerp(0, 1, Item.Repairables.Average(r => r.DegreeOfSuccess(character)));
|
||||||
float isSelected = character.SelectedConstruction == Item ? 50 : 0;
|
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);
|
return MathHelper.Clamp(baseLevel * damagePriority * distanceFactor * successFactor, 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +84,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (character.CanInteractWith(Item))
|
if (character.CanInteractWith(Item))
|
||||||
{
|
{
|
||||||
|
OperateRepairTool(deltaTime);
|
||||||
foreach (Repairable repairable in Item.Repairables)
|
foreach (Repairable repairable in Item.Repairables)
|
||||||
{
|
{
|
||||||
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
|
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
|
||||||
@@ -123,5 +124,33 @@ namespace Barotrauma
|
|||||||
AddSubObjective(goToObjective);
|
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<RepairTool>();
|
||||||
|
if (repairTool != null)
|
||||||
|
{
|
||||||
|
character.CursorPosition = Item.Position;
|
||||||
|
character.SetInput(InputType.Aim, false, true);
|
||||||
|
repairTool.Use(deltaTime, character);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -549,8 +549,9 @@ namespace Barotrauma
|
|||||||
limbHealths[i].Afflictions.RemoveAt(j);
|
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);
|
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == i);
|
||||||
affliction.Update(this, targetLimb, deltaTime);
|
affliction.Update(this, targetLimb, deltaTime);
|
||||||
affliction.DamagePerSecondTimer += deltaTime;
|
affliction.DamagePerSecondTimer += deltaTime;
|
||||||
|
|||||||
@@ -120,18 +120,30 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void CreateEvents()
|
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--)
|
for (int i = selectedEventSets.Count - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
ScriptedEventSet eventSet = selectedEventSets[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(
|
float distanceTraveled = MathHelper.Clamp(
|
||||||
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
|
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
|
||||||
0.0f, 1.0f);
|
0.0f, 1.0f);
|
||||||
|
|
||||||
if (Level.Loaded?.StartOutpost != null &&
|
//don't create new events if within 50 meters of the start/end of the level
|
||||||
Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost))
|
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) &&
|
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) &&
|
||||||
|
|||||||
@@ -65,12 +65,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public int ParticleLimit { get; set; }
|
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 int ParticleLimit { get; set; }
|
||||||
|
|
||||||
public float LightMapScale { get; set; }
|
public float LightMapScale { get; set; }
|
||||||
@@ -407,8 +401,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];
|
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
|
||||||
keyMapping[(int)InputType.Up] = new KeyOrMouse(Keys.W);
|
keyMapping[(int)InputType.Up] = new KeyOrMouse(Keys.W);
|
||||||
keyMapping[(int)InputType.Down] = new KeyOrMouse(Keys.S);
|
keyMapping[(int)InputType.Down] = new KeyOrMouse(Keys.S);
|
||||||
@@ -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
|
//save to get rid of the invalid selected packages in the config file
|
||||||
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
|
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
|
||||||
}
|
}
|
||||||
@@ -1084,6 +984,369 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
#endregion
|
#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<string>();
|
||||||
|
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<string>();
|
||||||
|
|
||||||
|
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<string> contentPackagePaths)
|
||||||
|
{
|
||||||
|
var missingPackagePaths = new List<string>();
|
||||||
|
var incompatiblePackages = new List<ContentPackage>();
|
||||||
|
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
|
#region Save PlayerConfig
|
||||||
public void SaveNewPlayerConfig()
|
public void SaveNewPlayerConfig()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -438,7 +438,7 @@ namespace Barotrauma.Items.Components
|
|||||||
GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
|
GameServer.Log(character.LogName + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
item.Drop();
|
item.Drop(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttachToWall();
|
AttachToWall();
|
||||||
|
|||||||
@@ -294,9 +294,10 @@ namespace Barotrauma.Items.Components
|
|||||||
character.CursorPosition = leak.Position;
|
character.CursorPosition = leak.Position;
|
||||||
character.SetInput(InputType.Aim, false, true);
|
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);
|
Use(deltaTime, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ namespace Barotrauma.Items.Components
|
|||||||
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
item.Drop();
|
item.Drop(picker);
|
||||||
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
|
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
|
||||||
|
|
||||||
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
|
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ namespace Barotrauma.Items.Components
|
|||||||
foreach (Item item in Inventory.Items)
|
foreach (Item item in Inventory.Items)
|
||||||
{
|
{
|
||||||
if (item == null) continue;
|
if (item == null) continue;
|
||||||
item.Drop();
|
item.Drop(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (inputContainer.Inventory.Items.All(it => it != null))
|
if (inputContainer.Inventory.Items.All(it => it != null))
|
||||||
{
|
{
|
||||||
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
|
var unneededItem = inputContainer.Inventory.Items.FirstOrDefault(it => !usedItems.Contains(it));
|
||||||
unneededItem?.Drop();
|
unneededItem?.Drop(null);
|
||||||
}
|
}
|
||||||
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
|
inputContainer.Inventory.TryPutItem(matchingItem, user: null, createNetworkEvent: true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (item != null && item.Condition <= 0.0f)
|
if (item != null && item.Condition <= 0.0f)
|
||||||
{
|
{
|
||||||
item.Drop();
|
item.Drop(character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private void Launch(Vector2 impulse)
|
private void Launch(Vector2 impulse)
|
||||||
{
|
{
|
||||||
item.Drop();
|
item.Drop(null);
|
||||||
|
|
||||||
item.body.Enabled = true;
|
item.body.Enabled = true;
|
||||||
item.body.ApplyLinearImpulse(impulse);
|
item.body.ApplyLinearImpulse(impulse);
|
||||||
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
float rotation = item.body.Rotation;
|
float rotation = item.body.Rotation;
|
||||||
Vector2 simPositon = item.SimPosition;
|
Vector2 simPositon = item.SimPosition;
|
||||||
item.Drop();
|
item.Drop(null);
|
||||||
|
|
||||||
item.body.Enabled = true;
|
item.body.Enabled = true;
|
||||||
//set the velocity of the body because the OnProjectileCollision method
|
//set the velocity of the body because the OnProjectileCollision method
|
||||||
|
|||||||
@@ -350,7 +350,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
reload = reloadTime;
|
reload = reloadTime;
|
||||||
|
|
||||||
projectile.Drop();
|
projectile.Drop(null);
|
||||||
projectile.body.Dir = 1.0f;
|
projectile.body.Dir = 1.0f;
|
||||||
|
|
||||||
projectile.body.ResetDynamics();
|
projectile.body.ResetDynamics();
|
||||||
|
|||||||
@@ -1563,7 +1563,7 @@ namespace Barotrauma
|
|||||||
return isCombined;
|
return isCombined;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Drop(Character dropper = null)
|
public void Drop(Character dropper)
|
||||||
{
|
{
|
||||||
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
|
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
|
||||||
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
|
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
|
||||||
|
|||||||
@@ -430,13 +430,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
CurrentLocation.SelectedMissionIndex = missionIndex;
|
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;
|
SelectedLocation = location;
|
||||||
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||||
|
|||||||
@@ -467,6 +467,7 @@ namespace Barotrauma
|
|||||||
public List<Submarine> GetConnectedSubs()
|
public List<Submarine> GetConnectedSubs()
|
||||||
{
|
{
|
||||||
connectedSubs.Clear();
|
connectedSubs.Clear();
|
||||||
|
connectedSubs.Add(this);
|
||||||
GetConnectedSubsRecursive(connectedSubs);
|
GetConnectedSubsRecursive(connectedSubs);
|
||||||
|
|
||||||
return connectedSubs;
|
return connectedSubs;
|
||||||
@@ -522,6 +523,30 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
|
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)
|
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
|
||||||
@@ -655,7 +680,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, List<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
|
public static Body PickBody(Vector2 rayStart, Vector2 rayEnd, IEnumerable<Body> ignoredBodies = null, Category? collisionCategory = null, bool ignoreSensors = true, Predicate<Fixture> customPredicate = null)
|
||||||
{
|
{
|
||||||
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
|
if (Vector2.DistanceSquared(rayStart, rayEnd) < 0.00001f)
|
||||||
{
|
{
|
||||||
@@ -1011,6 +1036,31 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the sub is same as the other.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsConnectedTo(Submarine otherSub) => this == otherSub || GetConnectedSubs().Contains(otherSub);
|
||||||
|
|
||||||
|
public List<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList);
|
||||||
|
public List<Gap> GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
|
||||||
|
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
|
||||||
|
|
||||||
|
public List<T> GetEntities<T>(bool includingConnectedSubs, List<T> 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;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the sub whose borders contain the position
|
/// Finds the sub whose borders contain the position
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -160,16 +160,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public void SetState()
|
|
||||||
{
|
|
||||||
hit = binding.IsHit();
|
|
||||||
if (hit) hitQueue = true;
|
|
||||||
|
|
||||||
held = binding.IsDown();
|
|
||||||
if (held) heldQueue = true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public bool Hit
|
public bool Hit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user