(f417b026f) Fetched: Changes for playing video tutorial from local branch

This commit is contained in:
Joonas Rikkonen
2019-03-27 20:45:14 +02:00
parent e65c669eaa
commit 3f82c9a2cb
125 changed files with 1317 additions and 3166 deletions
@@ -18,13 +18,14 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Ragdoll.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Ragdoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Attack.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Attack.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Character.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Character.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\CharacterHealth.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterHealth.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterHUD.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterHUD.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterInfo.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterNetworking.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterNetworking.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterSound.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterSound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionHusk.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionHusk.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionPsychosis.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionPsychosis.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\CharacterHealth.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\DamageModifier.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\DamageModifier.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\HUDProgressBar.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\HUDProgressBar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Jobs\JobPrefab.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Jobs\JobPrefab.cs" />
@@ -31,7 +31,6 @@
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
<ApplicationIcon>..\BarotraumaShared\IconBmp.bmp</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
@@ -192,11 +191,6 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Import Project="ClientCode.projitems" Label="Shared" /> <Import Project="ClientCode.projitems" Label="Shared" />
<Import Project="..\BarotraumaShared\SharedCode.projitems" Label="Shared" /> <Import Project="..\BarotraumaShared\SharedCode.projitems" Label="Shared" />
<Import Project="..\BarotraumaShared\SharedContent.projitems" Label="Shared" /> <Import Project="..\BarotraumaShared\SharedContent.projitems" Label="Shared" />
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.8.9.7")] [assembly: AssemblyVersion("0.8.9.6")]
[assembly: AssemblyFileVersion("0.8.9.7")] [assembly: AssemblyFileVersion("0.8.9.6")]
@@ -14,7 +14,6 @@ float2 uvTopLeft;
float2 uvBottomRight; float2 uvBottomRight;
float4 tintColor; float4 tintColor;
float4 solidColor;
struct VertexShaderInput struct VertexShaderInput
{ {
@@ -75,11 +74,6 @@ 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
@@ -87,13 +81,4 @@ technique DeformShader
VertexShader = compile vs_4_0_level_9_1 mainVS(); VertexShader = compile vs_4_0_level_9_1 mainVS();
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();
}
} }
@@ -14,7 +14,6 @@ float2 uvTopLeft;
float2 uvBottomRight; float2 uvBottomRight;
float4 tintColor; float4 tintColor;
float4 solidColor;
struct VertexShaderInput struct VertexShaderInput
{ {
@@ -75,11 +74,6 @@ 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
@@ -87,13 +81,4 @@ technique DeformShader
VertexShader = compile vs_3_0 mainVS(); VertexShader = compile vs_3_0 mainVS();
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();
}
} }
@@ -13,39 +13,13 @@ namespace Barotrauma
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y); var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
if (soundRange > 0.0f) if (soundRange > 0.0f)
{ {
Color color; Color color = Entity is Character ? Color.Yellow : Color.Orange;
if (Entity is Character)
{
color = Color.Yellow;
}
else if (Entity is Item)
{
color = Color.Orange;
}
else
{
color = Color.OrangeRed;
}
ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); ShapeExtensions.DrawCircle(spriteBatch, pos, SoundRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
ShapeExtensions.DrawCircle(spriteBatch, pos, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
} }
if (sightRange > 0.0f) if (sightRange > 0.0f)
{ {
Color color; Color color = Entity is Character ? Color.CornflowerBlue : Color.CadetBlue;
if (Entity is Character)
{
color = Color.CornflowerBlue;
}
else if (Entity is Item)
{
color = Color.CadetBlue;
}
else
{
color = Color.WhiteSmoke;
}
ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
ShapeExtensions.DrawCircle(spriteBatch, pos, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
} }
} }
} }
@@ -16,17 +16,18 @@ namespace Barotrauma
if (SelectedAiTarget?.Entity != null) if (SelectedAiTarget?.Entity != null)
{ {
GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), Color.Red * 0.5f, 0, 4); GUI.DrawLine(spriteBatch, pos, new Vector2(SelectedAiTarget.WorldPosition.X, -SelectedAiTarget.WorldPosition.Y), Color.Red * 0.3f, 0, 5);
if (wallTarget != null) if (wallTarget != null)
{ {
Vector2 wallTargetPos = wallTarget.Position; Vector2 wallTargetPos = wallTarget.Position;
if (wallTarget.Structure.Submarine != null) { wallTargetPos += wallTarget.Structure.Submarine.Position; } if (wallTarget.Structure.Submarine != null) wallTargetPos += wallTarget.Structure.Submarine.Position;
wallTargetPos.Y = -wallTargetPos.Y; wallTargetPos.Y = -wallTargetPos.Y;
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false); GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Red, false);
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5); GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
} }
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.FormatZeroDecimal()})", Color.Red, Color.Black);
GUI.Font.DrawString(spriteBatch, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.ToString()})", pos - Vector2.UnitY * 20.0f, Color.Red);
} }
/*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red); /*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red);
@@ -57,9 +58,18 @@ namespace Barotrauma
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorA.X, -attachJoint.WorldAnchorA.Y)), ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorA.X, -attachJoint.WorldAnchorA.Y)),
ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), Color.Green, 0, 4); ConvertUnits.ToDisplayUnits(new Vector2(attachJoint.WorldAnchorB.X, -attachJoint.WorldAnchorB.Y)), Color.Orange * 0.6f, 0, 5);
} }
if (latchOntoAI.WallAttachPos.HasValue)
{
GUI.DrawLine(spriteBatch, pos,
ConvertUnits.ToDisplayUnits(new Vector2(latchOntoAI.WallAttachPos.Value.X, -latchOntoAI.WallAttachPos.Value.Y)), Color.Orange * 0.6f, 0, 3);
}
}
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
if (latchOntoAI.WallAttachPos.HasValue) if (latchOntoAI.WallAttachPos.HasValue)
{ {
GUI.DrawLine(spriteBatch, pos, GUI.DrawLine(spriteBatch, pos,
@@ -67,34 +77,22 @@ namespace Barotrauma
} }
} }
if (steeringManager is IndoorsSteeringManager pathSteering) GUI.DrawLine(spriteBatch,
new Vector2(Character.DrawPosition.X, -Character.DrawPosition.Y),
new Vector2(pathSteering.CurrentPath.CurrentNode.DrawPosition.X, -pathSteering.CurrentPath.CurrentNode.DrawPosition.Y),
Color.Orange * 0.6f, 0, 3);
for (int i = 1; i < pathSteering.CurrentPath.Nodes.Count; i++)
{ {
var path = pathSteering.CurrentPath; GUI.DrawLine(spriteBatch,
if (path != null) new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y),
{ new Vector2(pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i - 1].DrawPosition.Y),
if (path.CurrentNode != null) Color.Orange * 0.6f, 0, 3);
{
GUI.DrawLine(spriteBatch, pos,
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
Color.DarkViolet, 0, 3);
GUI.DrawString(spriteBatch, pos - new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f); GUI.SmallFont.DrawString(spriteBatch,
} pathSteering.CurrentPath.Nodes[i].ID.ToString(),
for (int i = 1; i < path.Nodes.Count; i++) new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10),
{ Color.LightGreen);
var previousNode = path.Nodes[i - 1];
var currentNode = path.Nodes[i];
GUI.DrawLine(spriteBatch,
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y),
new Vector2(previousNode.DrawPosition.X, -previousNode.DrawPosition.Y),
Color.Red * 0.5f, 0, 3);
GUI.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20),
Color.Red);
}
}
} }
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2); GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
@@ -75,12 +75,11 @@ namespace Barotrauma
GUI.SmallFont.DrawString(spriteBatch, GUI.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(), currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20), new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y - 10),
Color.SkyBlue); Color.LightGreen);
} }
} }
} }
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Character.AnimController.TargetMovement.X, -Character.AnimController.TargetMovement.Y)), Color.SteelBlue, width: 2);
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3); GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
//if (Character.IsKeyDown(InputType.Aim)) //if (Character.IsKeyDown(InputType.Aim))
@@ -210,9 +210,7 @@ namespace Barotrauma
else else
{ {
Vector2 positionError = serverPos.Position - localPos.Position; Vector2 positionError = serverPos.Position - localPos.Position;
float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ? float rotationError = serverPos.Rotation - localPos.Rotation;
serverPos.Rotation.Value - localPos.Rotation.Value :
0.0f;
for (int i = localPosIndex; i < character.MemLocalState.Count; i++) for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
{ {
@@ -544,10 +544,7 @@ namespace Barotrauma
{ {
healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(Vitality / MaxVitality, Color.Red, Color.Orange, Color.Green); healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(Vitality / MaxVitality, Color.Red, Color.Orange, Color.Green);
healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f; healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f;
healthBar.BarSize = healthWindowHealthBar.BarSize = healthBar.BarSize = healthWindowHealthBar.BarSize = (Vitality > 0.0f) ? Vitality / MaxVitality : 1.0f - Vitality / MinVitality;
(Vitality > 0.0f) ?
(MaxVitality > 0.0f ? Vitality / MaxVitality : 0.0f) :
(Math.Abs(MinVitality) > 0.0f ? 1.0f - Vitality / MinVitality : 0.0f);
if (healthBarPulsateTimer > 0.0f) if (healthBarPulsateTimer > 0.0f)
{ {
@@ -214,8 +214,8 @@ namespace Barotrauma
msg.ReadRangedSingle(-MaxVel, MaxVel, 12)); msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
bool fixedRotation = msg.ReadBoolean(); bool fixedRotation = msg.ReadBoolean();
float? rotation = null; float rotation = AnimController.Collider.Rotation;
float? angularVelocity = null; float angularVelocity = AnimController.Collider.AngularVelocity;
if (!fixedRotation) if (!fixedRotation)
{ {
rotation = msg.ReadFloat(); rotation = msg.ReadFloat();
File diff suppressed because it is too large Load Diff
@@ -403,11 +403,6 @@ namespace Barotrauma
AssignRelayToServer("help", false); AssignRelayToServer("help", false);
AssignRelayToServer("verboselogging", false); AssignRelayToServer("verboselogging", false);
AssignRelayToServer("freecam", false); AssignRelayToServer("freecam", false);
#if DEBUG
AssignRelayToServer("simulatedlatency", false);
AssignRelayToServer("simulatedloss", false);
AssignRelayToServer("simulatedduplicateschance", false);
#endif
commands.Add(new Command("clientlist", "", (string[] args) => { })); commands.Add(new Command("clientlist", "", (string[] args) => { }));
AssignRelayToServer("clientlist", true); AssignRelayToServer("clientlist", true);
@@ -444,7 +439,7 @@ namespace Barotrauma
AssignOnExecute("ambientlight", (string[] args) => AssignOnExecute("ambientlight", (string[] args) =>
{ {
Color color = XMLExtensions.ParseColor(string.Join(",", args)); Color color = XMLExtensions.ParseColor(string.Join("", args));
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
Level.Loaded.GenerationParams.AmbientLightColor = color; Level.Loaded.GenerationParams.AmbientLightColor = color;
@@ -533,27 +528,44 @@ namespace Barotrauma
} }
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("resetall", "Reset all items and structures to prefabs. Only applicable in the subeditor.", args => commands.Add(new Command("alpha", "Change the alpha (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (string[] args) =>
{ {
if (Screen.Selected == GameMain.SubEditorScreen) if (Screen.Selected == GameMain.SubEditorScreen)
{ {
Item.ItemList.ForEach(i => i.Reset()); if (!MapEntity.SelectedAny)
Structure.WallList.ForEach(s => s.Reset());
foreach (MapEntity entity in MapEntity.SelectedList)
{ {
if (entity is Item item) ThrowError("You have to select item(s)/structure(s) first!");
}
else
{
if (args.Length > 0)
{ {
item.CreateEditingHUD(); if (!byte.TryParse(args[0], out byte a))
break; {
ThrowError($"Failed to parse value for ALPHA from {args[0]}");
}
else
{
foreach (var mapEntity in MapEntity.SelectedList)
{
if (mapEntity is Structure s)
{
s.SpriteColor = new Color(s.SpriteColor.R, s.SpriteColor.G, s.SpriteColor.G, a);
}
else if (mapEntity is Item i)
{
i.SpriteColor = new Color(i.SpriteColor.R, i.SpriteColor.G, i.SpriteColor.G, a);
}
}
}
} }
else if (entity is Structure structure) else
{ {
structure.CreateEditingHUD(); ThrowError("Not enough arguments provided! One required!");
break;
} }
} }
} }
})); }, isCheat: true));
commands.Add(new Command("alpha", "Change the alpha (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (string[] args) => commands.Add(new Command("alpha", "Change the alpha (as bytes from 0 to 255) of the selected item/structure instances. Applied only in the subeditor.", (string[] args) =>
{ {
@@ -0,0 +1,215 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
using System.Collections.Generic;
using Barotrauma.Media;
namespace Barotrauma
{
class VideoPlayer
{
private Video currentVideo;
private List<PreloadedContent> preloadedVideos;
private GUIFrame background, videoFrame;
private GUITextBlock title;
private GUICustomComponent videoView;
private Color backgroundColor = new Color(0f, 0f, 0f, 1f);
private Action callbackOnStop;
private bool isPlaying;
public bool IsPlaying()
{
return isPlaying;
/*if (currentVideo == null) return false;
return currentVideo.IsPlaying;*/
}
private readonly Point defaultResolution = new Point(520, 300);
private readonly int borderSize = 20;
private class PreloadedContent
{
public string ContentName;
public string ContentTag;
public Video Video;
public Point Resolution;
public PreloadedContent(string name, string tag, Video video, Point resolution)
{
ContentName = name;
ContentTag = tag;
Video = video;
Resolution = resolution;
}
}
public VideoPlayer()
{
int width = defaultResolution.X;
int height = defaultResolution.Y;
background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), "InnerFrame", backgroundColor);
videoFrame = new GUIFrame(new RectTransform(new Point(width + borderSize, height + borderSize), background.RectTransform, Anchor.Center), "SonarFrame");
videoView = new GUICustomComponent(new RectTransform(new Point(width, height), videoFrame.RectTransform, Anchor.Center),
(spriteBatch, guiCustomComponent) => { DrawVideo(spriteBatch, guiCustomComponent.Rect); });
title = new GUITextBlock(new RectTransform(new Vector2(1f, 0f), videoFrame.RectTransform, Anchor.TopCenter, Pivot.BottomCenter), string.Empty, font: GUI.LargeFont, textAlignment: Alignment.Center);
preloadedVideos = new List<PreloadedContent>();
}
public void PreloadContent(string contentPath, string contentTag, string contentId, XElement contentElement)
{
if (preloadedVideos.Find(s => s.ContentName == contentId) != null) return; // Already loaded
Point resolution = new Point(contentElement.GetAttributeInt("width", 0), contentElement.GetAttributeInt("height", 0));
if (resolution.X == 0 || resolution.Y == 0)
{
resolution = defaultResolution;
}
preloadedVideos.Add(new PreloadedContent(contentId, contentTag, CreateVideo(contentPath, resolution), resolution));
}
public void RemoveAllPreloaded()
{
if (preloadedVideos == null || preloadedVideos.Count == 0) return;
for (int i = 0; i < preloadedVideos.Count; i++)
{
preloadedVideos[i] = null;
}
preloadedVideos.Clear();
}
public void RemovePreloadedByTag(string tag)
{
if (preloadedVideos == null || preloadedVideos.Count == 0) return;
for (int i = 0; i < preloadedVideos.Count; i++)
{
if (preloadedVideos[i].ContentTag != tag) continue;
preloadedVideos[i] = null;
preloadedVideos.RemoveAt(i);
i--;
}
}
public void Play()
{
isPlaying = true;
}
public void Stop()
{
isPlaying = false;
if (currentVideo == null) return;
currentVideo.Dispose();
currentVideo = null;
}
private bool OKButtonClicked(GUIButton button, object userData)
{
Stop();
callbackOnStop?.Invoke();
return true;
}
public void AddToGUIUpdateList()
{
if (!IsPlaying()) return;
background.AddToGUIUpdateList();
}
public void LoadContent(string contentPath, XElement videoElement, string contentId, bool startPlayback, bool hasButton, Action callback = null)
{
if (currentVideo != null)
{
currentVideo.Dispose();
currentVideo = null;
}
PreloadedContent preloaded = null;
Point resolution = new Point(0, 0);
if (preloadedVideos != null && preloadedVideos.Count > 0)
{
preloaded = preloadedVideos.Find(s => s.ContentName == contentId);
if (preloaded != null)
{
currentVideo = preloaded.Video;
resolution = preloaded.Resolution;
}
}
if (currentVideo == null) // No preloaded sheets found, create sheets
{
resolution = new Point(videoElement.GetAttributeInt("width", 0), videoElement.GetAttributeInt("height", 0));
if (resolution.X == 0 || resolution.Y == 0)
{
resolution = defaultResolution;
}
currentVideo = CreateVideo(contentPath, resolution);
}
videoFrame.RectTransform.NonScaledSize = resolution + new Point(borderSize, borderSize);
videoView.RectTransform.NonScaledSize = resolution;
title.Text = TextManager.Get(contentId);
title.RectTransform.NonScaledSize = new Point(resolution.X, 30);
callbackOnStop = callback;
if (hasButton)
{
var okButton = new GUIButton(new RectTransform(new Point(160, 50), videoFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, -10) },
TextManager.Get("OK"))
{
OnClicked = OKButtonClicked
};
}
if (startPlayback) Play();
}
private Video CreateVideo(string contentPath, Point resolution)
{
Video video = null;
try
{
video = new Video(GameMain.Instance.GraphicsDevice, GameMain.SoundManager, contentPath, (uint)resolution.X, (uint)resolution.Y);
}
catch (Exception e)
{
DebugConsole.ThrowError("Error loading video content " + contentPath + "!", e);
}
return video;
}
private void DrawVideo(SpriteBatch spriteBatch, Rectangle rect)
{
if (!isPlaying) return;
spriteBatch.Draw(currentVideo.GetTexture(), rect, Color.White);
}
public void Remove()
{
if (currentVideo != null)
{
currentVideo.Dispose();
currentVideo = null;
}
RemoveAllPreloaded();
}
}
}
@@ -12,7 +12,7 @@ namespace Barotrauma
{ {
private UInt16 startWatchmanID, endWatchmanID; private UInt16 startWatchmanID, endWatchmanID;
public static GUIComponent StartCampaignSetup( IEnumerable<Submarine> submarines, IEnumerable<string> saveFiles) public static GUIComponent StartCampaignSetup(IEnumerable<string> saveFiles)
{ {
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker"); GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
@@ -35,7 +35,7 @@ namespace Barotrauma
var newCampaignContainer = new GUIFrame(new RectTransform(Vector2.One, campaignContainer.RectTransform, Anchor.BottomLeft), style: null); var newCampaignContainer = new GUIFrame(new RectTransform(Vector2.One, campaignContainer.RectTransform, Anchor.BottomLeft), style: null);
var loadCampaignContainer = new GUIFrame(new RectTransform(Vector2.One, campaignContainer.RectTransform, Anchor.BottomLeft), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(Vector2.One, campaignContainer.RectTransform, Anchor.BottomLeft), style: null);
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, submarines, saveFiles); var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign")) TextManager.Get("NewCampaign"))
@@ -92,7 +92,8 @@ namespace Barotrauma
if (endWatchman != null) { InitializeWatchman(endWatchman); } if (endWatchman != null) { InitializeWatchman(endWatchman); }
} }
} }
protected override void WatchmanInteract(Character watchman, Character interactor) protected override void WatchmanInteract(Character watchman, Character interactor)
{ {
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) || if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
@@ -106,7 +107,8 @@ namespace Barotrauma
return; return;
} }
if (GameMain.Client != null && interactor == Character.Controlled) if (GameMain.Client != null && interactor == Character.Controlled &&
(GameMain.Client.HasPermission(ClientPermissions.ManageRound) || GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)))
{ {
var msgBox = new GUIMessageBox("", TextManager.Get("CampaignEnterOutpostPrompt") var msgBox = new GUIMessageBox("", TextManager.Get("CampaignEnterOutpostPrompt")
.Replace("[locationname]", Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name), .Replace("[locationname]", Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
@@ -120,7 +122,8 @@ namespace Barotrauma
return true; return true;
}; };
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close; msgBox.Buttons[1].OnClicked += msgBox.Close;
} }
} }
@@ -19,6 +19,8 @@ namespace Barotrauma.Tutorials
private List<TutorialSegment> segments; private List<TutorialSegment> segments;
private SpriteSheetPlayer spriteSheetPlayer; private SpriteSheetPlayer spriteSheetPlayer;
private VideoPlayer videoPlayer;
private Steering navConsole; private Steering navConsole;
private Reactor reactor; private Reactor reactor;
private Sonar sonar; private Sonar sonar;
@@ -29,7 +31,7 @@ namespace Barotrauma.Tutorials
private bool started = false; private bool started = false;
private string playableContentPath; private string playableContentPath;
private float tutorialTimer; private float tutorialTimer;
private float degrading2ActivationCountdown; private float degrading2ActivationCountdown;
@@ -82,6 +84,7 @@ namespace Barotrauma.Tutorials
base.Initialize(); base.Initialize();
spriteSheetPlayer = new SpriteSheetPlayer(); spriteSheetPlayer = new SpriteSheetPlayer();
videoPlayer = new VideoPlayer();
characterTimeOnSonar = new List<Pair<Character, float>>(); characterTimeOnSonar = new List<Pair<Character, float>>();
for (int i = 0; i < segments.Count; i++) for (int i = 0; i < segments.Count; i++)
@@ -210,6 +213,8 @@ namespace Barotrauma.Tutorials
started = ContentRunning = Initialized = false; started = ContentRunning = Initialized = false;
spriteSheetPlayer.Remove(); spriteSheetPlayer.Remove();
spriteSheetPlayer = null; spriteSheetPlayer = null;
videoPlayer.Remove();
videoPlayer = null;
characterTimeOnSonar = null; characterTimeOnSonar = null;
} }
@@ -220,6 +225,10 @@ namespace Barotrauma.Tutorials
{ {
spriteSheetPlayer.AddToGUIUpdateList(); spriteSheetPlayer.AddToGUIUpdateList();
} }
if (videoPlayer != null)
{
videoPlayer.AddToGUIUpdateList();
}
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
@@ -325,7 +334,7 @@ namespace Barotrauma.Tutorials
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (!item.Repairables.Any() || item.ConditionPercentage > 50) continue; if (!item.Repairables.Any() || item.Condition > 50.0f) continue;
degradedEquipmentFound = true; degradedEquipmentFound = true;
break; break;
} }
@@ -461,10 +470,18 @@ namespace Barotrauma.Tutorials
case ContentTypes.None: case ContentTypes.None:
break; break;
case ContentTypes.Video: case ContentTypes.Video:
spriteSheetPlayer.LoadContent(playableContentPath, activeSegment.Content, activeSegment.Name, true, true, CurrentSegmentStopCallback); string fileName = activeSegment.Content.GetAttributeString("file", "");
if (fileName != "")
{
videoPlayer.LoadContent(playableContentPath + fileName, activeSegment.Content, activeSegment.Name, true, true, CurrentSegmentStopCallback);
}
else
{
spriteSheetPlayer.LoadContent(playableContentPath, activeSegment.Content, activeSegment.Name, true, true, CurrentSegmentStopCallback);
}
break; break;
case ContentTypes.Text: case ContentTypes.Text:
infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Name), TextManager.GetFormatted(activeSegment.Content.GetAttributeString("tag", ""), false, args), infoBox = CreateInfoFrame(TextManager.Get(activeSegment.Name), TextManager.Get(activeSegment.Content.GetAttributeString("tag", ""), false, args),
activeSegment.Content.GetAttributeInt("width", 300), activeSegment.Content.GetAttributeInt("width", 300),
activeSegment.Content.GetAttributeInt("height", 80), activeSegment.Content.GetAttributeInt("height", 80),
activeSegment.Content.GetAttributeString("anchor", "Center"), true, CurrentSegmentStopCallback); activeSegment.Content.GetAttributeString("anchor", "Center"), true, CurrentSegmentStopCallback);
@@ -47,7 +47,7 @@ namespace Barotrauma
//if "%" is found //if "%" is found
if (index > 0) if (index > 0)
{ {
while (index > 0) while (true)
{ {
//search for end of label //search for end of label
index -= 1; index -= 1;
@@ -439,6 +439,7 @@ namespace Barotrauma
} }
}; };
#endif #endif
var radioButtonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), voiceSettings.RectTransform)) var radioButtonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), voiceSettings.RectTransform))
{ {
Stretch = true, Stretch = true,
@@ -457,24 +458,6 @@ namespace Barotrauma
voiceMode.AddRadioButton((VoiceMode)i, tick); voiceMode.AddRadioButton((VoiceMode)i, tick);
} }
var micVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.18f), voiceSettings.RectTransform), TextManager.Get("MicrophoneVolume"));
var micVolumeSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.18f), voiceSettings.RectTransform),
barSize: 0.1f)
{
UserData = micVolumeText,
BarScroll = (float)Math.Sqrt(MathUtils.InverseLerp(0.2f, 5.0f, MicrophoneVolume)),
OnMoved = (scrollBar, scroll) =>
{
MicrophoneVolume = MathHelper.Lerp(0.2f, 5.0f, scroll * scroll);
MicrophoneVolume = (float)Math.Round(MicrophoneVolume, 1);
ChangeSliderText(scrollBar, MicrophoneVolume);
scrollBar.Step = 0.05f;
return true;
},
Step = 0.05f
};
micVolumeSlider.OnMoved(micVolumeSlider, micVolumeSlider.BarScroll);
var voiceInputContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.2f), voiceSettings.RectTransform, Anchor.BottomCenter)); var voiceInputContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.2f), voiceSettings.RectTransform, Anchor.BottomCenter));
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice") + ": "); new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice") + ": ");
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight),
@@ -486,6 +469,7 @@ namespace Barotrauma
voiceKeyBox.SelectedColor = Color.Gold * 0.3f; voiceKeyBox.SelectedColor = Color.Gold * 0.3f;
var voiceActivityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), voiceSettings.RectTransform)); var voiceActivityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), voiceSettings.RectTransform));
GUITextBlock noiseGateText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), voiceActivityGroup.RectTransform), TextManager.Get("NoiseGateThreshold")) GUITextBlock noiseGateText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), voiceActivityGroup.RectTransform), TextManager.Get("NoiseGateThreshold"))
{ {
TextGetter = () => TextGetter = () =>
@@ -525,13 +509,13 @@ namespace Barotrauma
if (GameMain.Client == null && VoipCapture.Instance == null) if (GameMain.Client == null && VoipCapture.Instance == null)
{ {
VoipCapture.Create(GameMain.Config.VoiceCaptureDevice); VoipCapture.Create(GameMain.Config.VoiceCaptureDevice);
if (VoipCapture.Instance == null) }
{ if (VoipCapture.Instance == null)
VoiceSetting = vMode = VoiceMode.Disabled; {
voiceInputContainer.Visible = false; VoiceSetting = vMode = VoiceMode.Disabled;
voiceActivityGroup.Visible = false; voiceInputContainer.Visible = false;
return; voiceActivityGroup.Visible = false;
} return;
} }
} }
else else
@@ -93,7 +93,7 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing) public void Draw(SpriteBatch spriteBatch, bool editing)
{ {
Color color = item.SpriteColor; Color color = Color.White;
if (brokenSprite == null) if (brokenSprite == null)
{ {
//broken doors turn black if no broken sprite has been configured //broken doors turn black if no broken sprite has been configured
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
weldSpritePos.Y = -weldSpritePos.Y; weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch, weldedSprite.Draw(spriteBatch,
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale); weldSpritePos, Color.White * (stuck / 100.0f), scale: item.Scale);
} }
if (openState == 1.0f) if (openState == 1.0f)
@@ -313,7 +313,7 @@ namespace Barotrauma.Items.Components
if (sound == null) { return 0.0f; } if (sound == null) { return 0.0f; }
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; } if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property)) if (properties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
{ {
float newVolume = 0.0f; float newVolume = 0.0f;
try try
@@ -331,18 +331,8 @@ namespace Barotrauma.Items.Components
spriteBatch.GraphicsDevice.ScissorRectangle = container.Rect; spriteBatch.GraphicsDevice.ScissorRectangle = container.Rect;
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
//make the pointer jitter a bit if it's at the upper limit of the fission rate
float jitter = 0.0f;
if (FissionRate > allowedFissionRate.Y - 5.0f)
{
float jitterAmount = Math.Min(targetFissionRate - allowedFissionRate.Y, 10.0f);
float t = graphTimer / updateGraphInterval;
jitter = (PerlinNoise.GetPerlin(t * 0.5f, t * 0.1f) - 0.5f) * jitterAmount;
}
DrawMeter(spriteBatch, container.Rect, DrawMeter(spriteBatch, container.Rect,
fissionRateMeter, FissionRate + jitter, new Vector2(0.0f, 100.0f), optimalFissionRate, allowedFissionRate); fissionRateMeter, FissionRate, new Vector2(0.0f, 100.0f), optimalFissionRate, allowedFissionRate);
spriteBatch.End(); spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
@@ -402,12 +392,12 @@ namespace Barotrauma.Items.Components
Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale); Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale);
Vector2 optimalRangeNormalized = new Vector2( Vector2 optimalRangeNormalized = new Vector2(
MathHelper.Clamp((optimalRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f), (optimalRange.X - range.X) / (range.Y - range.X),
MathHelper.Clamp((optimalRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f)); (optimalRange.Y - range.X) / (range.Y - range.X));
Vector2 allowedRangeNormalized = new Vector2( Vector2 allowedRangeNormalized = new Vector2(
MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f), (allowedRange.X - range.X) / (range.Y - range.X),
MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f)); (allowedRange.Y - range.X) / (range.Y - range.X));
Vector2 sectorRad = new Vector2(-1.57f, 1.57f); Vector2 sectorRad = new Vector2(-1.57f, 1.57f);
@@ -427,10 +417,10 @@ namespace Barotrauma.Items.Components
{ {
spriteBatch.End(); spriteBatch.End();
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3); spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0,0,GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale));
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
sectorSprite.Draw(spriteBatch, pos, Color.LightGreen, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale); sectorSprite.Draw(spriteBatch, pos, Color.LightGreen, MathHelper.PiOver2, scale);
sectorSprite.Draw(spriteBatch, pos, Color.Orange, optimalSectorRad.X, scale); sectorSprite.Draw(spriteBatch, pos, Color.Orange, optimalSectorRad.X, scale);
sectorSprite.Draw(spriteBatch, pos, Color.Red, allowedSectorRad.X, scale); sectorSprite.Draw(spriteBatch, pos, Color.Red, allowedSectorRad.X, scale);
@@ -785,9 +785,8 @@ namespace Barotrauma.Items.Components
foreach (Limb limb in c.AnimController.Limbs) foreach (Limb limb in c.AnimController.Limbs)
{ {
if (!limb.body.Enabled) { continue; }
float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared(); float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared();
if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) continue; if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) continue;
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr) if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
@@ -484,14 +484,26 @@ namespace Barotrauma.Items.Components
user = Character.Controlled; user = Character.Controlled;
} }
} }
if (!AutoPilot && Character.DisableControls && GUI.KeyboardDispatcher.Subscriber == null) if (!AutoPilot && Character.DisableControls)
{ {
steeringAdjustSpeed = character == null ? 0.2f : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f); steeringAdjustSpeed = character == null ? 0.2f : MathHelper.Lerp(0.2f, 1.0f, character.GetSkillLevel("helm") / 100.0f);
Vector2 input = Vector2.Zero; Vector2 input = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { input -= Vector2.UnitX; } if (PlayerInput.KeyDown(InputType.Left))
if (PlayerInput.KeyDown(InputType.Right)) { input += Vector2.UnitX; } {
if (PlayerInput.KeyDown(InputType.Up)) { input += Vector2.UnitY; } input -= Vector2.UnitX;
if (PlayerInput.KeyDown(InputType.Down)) { input -= Vector2.UnitY; } }
if (PlayerInput.KeyDown(InputType.Right))
{
input += Vector2.UnitX;
}
if (PlayerInput.KeyDown(InputType.Up))
{
input += Vector2.UnitY;
}
if (PlayerInput.KeyDown(InputType.Down))
{
input -= Vector2.UnitY;
}
if (PlayerInput.KeyDown(Keys.LeftShift)) if (PlayerInput.KeyDown(Keys.LeftShift))
{ {
SteeringInput += input * deltaTime * 200; SteeringInput += input * deltaTime * 200;
@@ -1,6 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Particles;
using Barotrauma.Particles;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic; using System.Collections.Generic;
@@ -114,7 +112,8 @@ namespace Barotrauma.Items.Components
System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts"); System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");
foreach (GUIComponent c in GuiFrame.GetChild(0).Children) foreach (GUIComponent c in GuiFrame.GetChild(0).Children)
{ {
if (!(c.UserData is Skill skill)) continue; Skill skill = c.UserData as Skill;
if (skill == null) continue;
GUITextBlock textBlock = (GUITextBlock)c; GUITextBlock textBlock = (GUITextBlock)c;
if (character.GetSkillLevel(skill.Identifier) < skill.Level) if (character.GetSkillLevel(skill.Identifier) < skill.Level)
@@ -127,15 +126,5 @@ namespace Barotrauma.Items.Components
} }
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
deteriorationTimer = msg.ReadSingle();
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
//no need to write anything, just letting the server know we started repairing
}
} }
} }
@@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>(); Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in allAfflictions) foreach (Affliction affliction in allAfflictions)
{ {
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) continue; if (affliction.Strength < affliction.Prefab.ActivationThreshold || affliction.Strength <= 0.0f) continue;
if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab)) if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
{ {
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength; combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
@@ -237,13 +237,13 @@ namespace Barotrauma.Items.Components
railSprite?.Draw(spriteBatch, railSprite?.Draw(spriteBatch,
drawPos, drawPos,
item.SpriteColor, Color.White,
rotation + MathHelper.PiOver2, item.Scale, rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch, barrelSprite?.Draw(spriteBatch,
drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale, drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale,
item.SpriteColor, Color.White,
rotation + MathHelper.PiOver2, item.Scale, rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
@@ -288,10 +288,7 @@ namespace Barotrauma.Items.Components
availableAmmo.AddRange(itemContainer.Inventory.Items); availableAmmo.AddRange(itemContainer.Inventory.Items);
} }
float chargeRate = float chargeRate = powerConsumption <= 0.0f ? 1.0f : batteryCharge / batteryCapacity;
powerConsumption <= 0.0f ?
1.0f :
batteryCapacity > 0.0f ? batteryCharge / batteryCapacity : 0.0f;
bool charged = batteryCharge * 3600.0f > powerConsumption; bool charged = batteryCharge * 3600.0f > powerConsumption;
bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null); bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null);
if (ShowChargeIndicator && PowerConsumption > 0.0f) if (ShowChargeIndicator && PowerConsumption > 0.0f)
@@ -448,7 +448,7 @@ namespace Barotrauma
} }
} }
public GUIComponent CreateEditingHUD(bool inGame = false) private GUIComponent CreateEditingHUD(bool inGame = false)
{ {
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null) GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
@@ -457,38 +457,40 @@ namespace Barotrauma
}; };
var itemEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true); var itemEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true);
if (!inGame)
if (!inGame && Linkable)
{ {
if (Linkable) var linkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, 20)), TextManager.Get("HoldToLink"), font: GUI.SmallFont);
var itemsText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, 20)), TextManager.Get("AllowedLinks") + ": ", font: GUI.SmallFont);
if (AllowedLinks.None())
{ {
var linkText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, 20)), TextManager.Get("HoldToLink"), font: GUI.SmallFont); itemsText.Text += TextManager.Get("None");
var itemsText = new GUITextBlock(new RectTransform(new Point(editingHUD.Rect.Width, 20)), TextManager.Get("AllowedLinks") + ": ", font: GUI.SmallFont); }
if (AllowedLinks.None()) else
{
for (int i = 0; i < AllowedLinks.Count; i++)
{ {
itemsText.Text += TextManager.Get("None"); itemsText.Text += AllowedLinks[i];
} if (i < AllowedLinks.Count - 1)
else
{
for (int i = 0; i < AllowedLinks.Count; i++)
{ {
itemsText.Text += AllowedLinks[i]; itemsText.Text += ", ";
if (i < AllowedLinks.Count - 1)
{
itemsText.Text += ", ";
}
} }
} }
itemEditor.AddCustomContent(linkText, 1);
itemEditor.AddCustomContent(itemsText, 2);
linkText.TextColor = Color.Yellow;
itemsText.TextColor = Color.Yellow;
} }
itemEditor.AddCustomContent(linkText, 1);
itemEditor.AddCustomContent(itemsText, 2);
linkText.TextColor = Color.Yellow;
itemsText.TextColor = Color.Yellow;
}
if (!inGame)
{
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, 20)), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, 20)), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX")) new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -497,7 +499,7 @@ namespace Barotrauma
return true; return true;
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY")) new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{ {
ToolTip = TextManager.Get("MirrorEntityYToolTip"), ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -508,7 +510,7 @@ namespace Barotrauma
}; };
if (Sprite != null) if (Sprite != null)
{ {
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite")); var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"));
reloadTextureButton.OnClicked += (button, data) => reloadTextureButton.OnClicked += (button, data) =>
{ {
Sprite.ReloadXML(); Sprite.ReloadXML();
@@ -516,15 +518,6 @@ namespace Barotrauma
return true; return true;
}; };
} }
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ResetToPrefab"))
{
OnClicked = (button, data) =>
{
Reset();
CreateEditingHUD();
return true;
}
};
itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount); itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount);
} }
+41 -11
View File
@@ -1,13 +1,14 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using System.Linq; using System.Linq;
using Lidgren.Network;
namespace Barotrauma namespace Barotrauma
{ {
@@ -18,8 +19,6 @@ namespace Barotrauma
private List<Decal> decals = new List<Decal>(); private List<Decal> decals = new List<Decal>();
private float serverUpdateDelay; private float serverUpdateDelay;
private float remoteWaterVolume, remoteOxygenPercentage;
private List<Vector3> remoteFireSources;
private bool networkUpdatePending; private bool networkUpdatePending;
private float networkUpdateTimer; private float networkUpdateTimer;
@@ -140,10 +139,6 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam) partial void UpdateProjSpecific(float deltaTime, Camera cam)
{ {
serverUpdateDelay -= deltaTime; serverUpdateDelay -= deltaTime;
if (serverUpdateDelay <= 0.0f)
{
ApplyRemoteState();
}
if (networkUpdatePending) if (networkUpdatePending)
{ {
@@ -552,18 +547,18 @@ namespace Barotrauma
public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime) public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime)
{ {
remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; float newWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8); float newOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
bool hasFireSources = message.ReadBoolean(); bool hasFireSources = message.ReadBoolean();
int fireSourceCount = 0; int fireSourceCount = 0;
remoteFireSources = new List<Vector3>(); List<Vector3> newFireSources = new List<Vector3>();
if (hasFireSources) if (hasFireSources)
{ {
fireSourceCount = message.ReadRangedInteger(0, 16); fireSourceCount = message.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++) for (int i = 0; i < fireSourceCount; i++)
{ {
remoteFireSources.Add(new Vector3( newFireSources.Add(new Vector3(
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f), MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f), MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
message.ReadRangedSingle(0.0f, 1.0f, 8))); message.ReadRangedSingle(0.0f, 1.0f, 8)));
@@ -572,6 +567,41 @@ namespace Barotrauma
if (serverUpdateDelay > 0.0f) { return; } if (serverUpdateDelay > 0.0f) { return; }
WaterVolume = newWaterVolume;
OxygenPercentage = newOxygenPercentage;
for (int i = 0; i < fireSourceCount; i++)
{
Vector2 pos = new Vector2(
rect.X + rect.Width * newFireSources[i].X,
rect.Y - rect.Height + (rect.Height * newFireSources[i].Y));
float size = newFireSources[i].Z * rect.Width;
var newFire = i < FireSources.Count ?
FireSources[i] :
new FireSource(Submarine == null ? pos : pos + Submarine.Position, null, true);
newFire.Position = pos;
newFire.Size = new Vector2(size, newFire.Size.Y);
//ignore if the fire wasn't added to this room (invalid position)?
if (!FireSources.Contains(newFire))
{
newFire.Remove();
continue;
}
}
for (int i = FireSources.Count - 1; i >= fireSourceCount; i--)
{
FireSources[i].Remove();
if (i < FireSources.Count)
{
FireSources.RemoveAt(i);
}
}
if (serverUpdateDelay > 0.0f) { return; }
ApplyRemoteState(); ApplyRemoteState();
} }
@@ -404,15 +404,12 @@ namespace Barotrauma.Lights
} }
} }
if (highlightedEntities.Count == 0) { return false; } if (highlightedEntities.Count == 0) { return false; }
//draw characters in light blue first //draw characters in solid white first
graphics.SetRenderTarget(HighlightMap); graphics.SetRenderTarget(HighlightMap);
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.LightBlue.ToVector4()); SolidColorEffect.Parameters["color"].SetValue(Color.LightBlue.ToVector4());
SolidColorEffect.CurrentTechnique.Passes[0].Apply(); SolidColorEffect.CurrentTechnique.Passes[0].Apply();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidColor"];
DeformableSprite.Effect.Parameters["solidColor"].SetValue(Color.LightBlue.ToVector4());
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Entity highlighted in highlightedEntities) foreach (Entity highlighted in highlightedEntities)
{ {
@@ -429,12 +426,9 @@ namespace Barotrauma.Lights
//draw characters in black with a bit of blur, leaving the white edges visible //draw characters in black with a bit of blur, leaving the white edges visible
float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1 float phase = (float)(Math.Sin(Timing.TotalTime * 3.0f) + 1.0f) / 2.0f; //phase oscillates between 0 and 1
Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase); SolidColorEffect.Parameters["color"].SetValue(Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase));
SolidColorEffect.Parameters["color"].SetValue(overlayColor);
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColorBlur"]; SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColorBlur"];
SolidColorEffect.CurrentTechnique.Passes[0].Apply(); SolidColorEffect.CurrentTechnique.Passes[0].Apply();
DeformableSprite.Effect.Parameters["solidColor"].SetValue(overlayColor);
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, samplerState: SamplerState.LinearWrap, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Entity highlighted in highlightedEntities) foreach (Entity highlighted in highlightedEntities)
{ {
@@ -456,8 +450,6 @@ namespace Barotrauma.Lights
spriteBatch.Draw(highlightRaster, new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), Color.White * 0.5f); spriteBatch.Draw(highlightRaster, new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), new Rectangle(0, 0, HighlightMap.Width, HighlightMap.Height), Color.White * 0.5f);
spriteBatch.End(); spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
return true; return true;
} }
@@ -479,10 +471,6 @@ namespace Barotrauma.Lights
Level.Loaded?.Renderer?.RenderWalls(graphics, cam, specular: true); Level.Loaded?.Renderer?.RenderWalls(graphics, cam, specular: true);
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidColor"];
DeformableSprite.Effect.Parameters["solidColor"].SetValue(Color.Gray.ToVector4());
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
//obstruct specular maps behind the sub and characters by drawing them on the map in solid gray //obstruct specular maps behind the sub and characters by drawing them on the map in solid gray
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"]; SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.Gray.ToVector4()); SolidColorEffect.Parameters["color"].SetValue(Color.Gray.ToVector4());
@@ -499,9 +487,6 @@ namespace Barotrauma.Lights
} }
spriteBatch.End(); spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
graphics.SetRenderTarget(null); graphics.SetRenderTarget(null);
graphics.BlendState = BlendState.AlphaBlend; graphics.BlendState = BlendState.AlphaBlend;
} }
@@ -364,23 +364,15 @@ namespace Barotrauma
} }
} }
if (GUI.KeyboardDispatcher.Subscriber == null)
{
float moveSpeed = 1000.0f;
Vector2 moveAmount = Vector2.Zero;
if (PlayerInput.KeyDown(InputType.Left)) { moveAmount += Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Right)) { moveAmount -= Vector2.UnitX; }
if (PlayerInput.KeyDown(InputType.Up)) { moveAmount += Vector2.UnitY; }
if (PlayerInput.KeyDown(InputType.Down)) { moveAmount -= Vector2.UnitY; }
drawOffset += moveAmount * moveSpeed / zoom * deltaTime;
}
if (GUI.MouseOn == mapContainer) if (GUI.MouseOn == mapContainer)
{ {
zoom += PlayerInput.ScrollWheelSpeed / 1000.0f; zoom += PlayerInput.ScrollWheelSpeed / 1000.0f;
zoom = MathHelper.Clamp(zoom, 1.0f, 4.0f); zoom = MathHelper.Clamp(zoom, 1.0f, 4.0f);
if (PlayerInput.MidButtonHeld()) { drawOffset += PlayerInput.MouseSpeed / zoom; } if (PlayerInput.MidButtonHeld())
{
drawOffset += PlayerInput.MouseSpeed / zoom;
}
#if DEBUG #if DEBUG
if (PlayerInput.DoubleClicked() && highlightedLocation != null) if (PlayerInput.DoubleClicked() && highlightedLocation != null)
{ {
@@ -559,6 +551,11 @@ namespace Barotrauma
null, connectionColor * MathHelper.Clamp(a, 0.1f, 0.5f), MathUtils.VectorToAngle(end - start), null, connectionColor * MathHelper.Clamp(a, 0.1f, 0.5f), MathUtils.VectorToAngle(end - start),
new Vector2(0, 16), SpriteEffects.None, 0.01f); new Vector2(0, 16), SpriteEffects.None, 0.01f);
} }
}
rect.Inflate(8, 8);
GUI.DrawRectangle(spriteBatch, rect, Color.Black, false, 0.0f, 8);
GUI.DrawRectangle(spriteBatch, rect, Color.LightGray);
if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames) if (GameMain.DebugDraw && zoom > 1.0f && generationParams.ShowLevelTypeNames)
{ {
@@ -98,7 +98,7 @@ namespace Barotrauma
Stretch = true, Stretch = true,
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX")) new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -107,7 +107,7 @@ namespace Barotrauma
return true; return true;
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY")) new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{ {
ToolTip = TextManager.Get("MirrorEntityYToolTip"), ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -116,7 +116,7 @@ namespace Barotrauma
return true; return true;
} }
}; };
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite")) var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"))
{ {
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
@@ -125,15 +125,6 @@ namespace Barotrauma
return true; return true;
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ResetToPrefab"))
{
OnClicked = (button, data) =>
{
Reset();
CreateEditingHUD();
return true;
}
};
editor.AddCustomContent(buttonContainer, editor.ContentCount); editor.AddCustomContent(buttonContainer, editor.ContentCount);
PositionEditingHUD(); PositionEditingHUD();
@@ -292,7 +283,8 @@ namespace Barotrauma
{ {
if (damageEffect != null) if (damageEffect != null)
{ {
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / Prefab.Health); float newCutoff = Sections[i].damage > 0 ?
MathHelper.Lerp(0.2f, 0.65f, Sections[i].damage / Prefab.Health) : 0.0f;
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor) if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor)
{ {
@@ -359,7 +351,6 @@ namespace Barotrauma
-Bodies[i].Rotation, Color.White); -Bodies[i].Rotation, Color.White);
} }
} }
AiTarget?.Draw(spriteBatch);
} }
} }
@@ -132,7 +132,6 @@ namespace Barotrauma.Networking
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform), var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterRight) isHorizontal: true, childAnchor: Anchor.CenterRight)
{ {
AbsoluteSpacing = 5,
CanBeFocused = false CanBeFocused = false
}; };
@@ -141,32 +140,16 @@ namespace Barotrauma.Networking
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!permissions.HasFlag(ClientPermissions.ManageRound)) { return false; } if (!permissions.HasFlag(ClientPermissions.ManageRound)) return false;
if (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition) RequestRoundEnd();
{
var msgBox = new GUIMessageBox("", TextManager.Get("EndRoundSubNotAtLevelEnd"),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = (_, __) =>
{
GameMain.Client.RequestRoundEnd();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
else
{
RequestRoundEnd();
}
return true; return true;
}, },
Visible = false Visible = false
}; };
EndVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.4f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) }, EndVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
TextManager.Get("EndRound")) TextManager.Get("EndRound"))
{ {
UserData = TextManager.Get("EndRound"),
OnSelected = ToggleEndRoundVote, OnSelected = ToggleEndRoundVote,
Visible = false Visible = false
}; };
@@ -340,17 +323,18 @@ namespace Barotrauma.Networking
// Loop until we are approved // Loop until we are approved
//TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly) //TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly)
string connectingText = TextManager.Get("Connecting"); string connectingText = TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP);
while (!CanStart && !connectCancelled) while (!CanStart && !connectCancelled)
{ {
if (reconnectBox == null) if (reconnectBox == null)
{ {
reconnectBox = new GUIMessageBox(connectingText, TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP), new string[] { TextManager.Get("Cancel") }); reconnectBox = new GUIMessageBox(TextManager.Get("Connecting"), connectingText, new string[] { TextManager.Get("Cancel") });
reconnectBox.Buttons[0].OnClicked += CancelConnect; reconnectBox.Buttons[0].OnClicked += CancelConnect;
reconnectBox.Buttons[0].OnClicked += reconnectBox.Close; reconnectBox.Buttons[0].OnClicked += reconnectBox.Close;
} }
reconnectBox.Header.Text = connectingText + new string('.', ((int)Timing.TotalTime % 3 + 1)); reconnectBox.Text.Text = connectingText + new string('.', ((int)Timing.TotalTime % 3 + 1));
if (DateTime.Now > reqAuthTime) if (DateTime.Now > reqAuthTime)
{ {
@@ -637,7 +621,7 @@ namespace Barotrauma.Networking
if (gameStarted) SetRadioButtonColor(); if (gameStarted) SetRadioButtonColor();
if (ShowNetStats && client?.ServerConnection != null) if (ShowNetStats)
{ {
netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes); netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes);
netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes); netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes);
@@ -798,7 +782,7 @@ namespace Barotrauma.Networking
saveFiles.Add(inc.ReadString()); saveFiles.Add(inc.ReadString());
} }
GameMain.NetLobbyScreen.CampaignSetupUI = MultiPlayerCampaign.StartCampaignSetup(serverSubmarines, saveFiles); GameMain.NetLobbyScreen.CampaignSetupUI = MultiPlayerCampaign.StartCampaignSetup(saveFiles);
break; break;
case ServerPacketHeader.PERMISSIONS: case ServerPacketHeader.PERMISSIONS:
ReadPermissions(inc); ReadPermissions(inc);
@@ -851,8 +835,6 @@ namespace Barotrauma.Networking
DisconnectReason disconnectReason = DisconnectReason.Unknown; DisconnectReason disconnectReason = DisconnectReason.Unknown;
if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason); if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason);
DebugConsole.NewMessage("Received a disconnect message (" + disconnectMsg + ")");
if (disconnectReason == DisconnectReason.ServerFull) if (disconnectReason == DisconnectReason.ServerFull)
{ {
//already waiting for a slot to free up, do nothing //already waiting for a slot to free up, do nothing
@@ -882,6 +864,14 @@ namespace Barotrauma.Networking
waitInServerQueueBox = null; waitInServerQueueBox = null;
CoroutineManager.StopCoroutines("WaitInServerQueue"); CoroutineManager.StopCoroutines("WaitInServerQueue");
} }
else
{
//disconnected/denied for some other reason than the server being full
// -> stop queuing and show a message box
waitInServerQueueBox?.Close();
waitInServerQueueBox = null;
CoroutineManager.StopCoroutines("WaitInServerQueue");
}
if (allowReconnect && disconnectReason == DisconnectReason.Unknown) if (allowReconnect && disconnectReason == DisconnectReason.Unknown)
{ {
@@ -911,6 +901,25 @@ namespace Barotrauma.Networking
DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection)."); DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection).");
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString()); msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
if (allowReconnect && disconnectReason == DisconnectReason.Unknown)
{
reconnectBox = new GUIMessageBox(
TextManager.Get("ConnectionLost"),
TextManager.Get("ConnectionLostReconnecting"), new string[0]);
connected = false;
ConnectToServer(serverIP);
}
else
{
string msg = "";
if (disconnectReason == DisconnectReason.Unknown)
{
msg = disconnectMsg;
}
else
{
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
for (int i = 1; i < splitMsg.Length; i++) for (int i = 1; i < splitMsg.Length; i++)
{ {
msg += TextManager.GetServerMessage(splitMsg[i]); msg += TextManager.GetServerMessage(splitMsg[i]);
@@ -1048,10 +1057,9 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false; EndVoteTickBox.Selected = false;
int seed = inc.ReadInt32(); int seed = inc.ReadInt32();
string levelSeed = inc.ReadString(); string levelSeed = inc.ReadString();
int levelEqualityCheckVal = inc.ReadInt32(); float levelDifficulty = inc.ReadFloat();
float levelDifficulty = inc.ReadFloat();
byte losMode = inc.ReadByte(); byte losMode = inc.ReadByte();
@@ -1120,15 +1128,6 @@ namespace Barotrauma.Networking
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
} }
if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
{
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
DebugConsole.ThrowError(errorMsg, createMessageBox: true);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
CoroutineManager.StartCoroutine(EndGame(""));
yield return CoroutineStatus.Failure;
}
if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); if (respawnAllowed) respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null);
gameStarted = true; gameStarted = true;
@@ -1198,8 +1197,8 @@ namespace Barotrauma.Networking
} }
} }
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, serverSubmarines); GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, submarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, serverSubmarines); GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, submarines);
gameStarted = inc.ReadBoolean(); gameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean(); bool allowSpectating = inc.ReadBoolean();
@@ -1392,6 +1391,9 @@ namespace Barotrauma.Networking
case ServerNetObject.CLIENT_LIST: case ServerNetObject.CLIENT_LIST:
ReadClientList(inc); ReadClientList(inc);
break; break;
case ServerNetObject.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.CHAT_MESSAGE: case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc); ChatMessage.ClientRead(inc);
break; break;
@@ -1715,8 +1717,6 @@ namespace Barotrauma.Networking
SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession); SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession);
campaign.LastSaveID = campaign.PendingSaveID; campaign.LastSaveID = campaign.PendingSaveID;
DebugConsole.Log("Campaign save received, save ID " + campaign.LastSaveID);
//decrement campaign update ID so the server will send us the latest data //decrement campaign update ID so the server will send us the latest data
//(as there may have been campaign updates after the save file was created) //(as there may have been campaign updates after the save file was created)
campaign.LastUpdateID--; campaign.LastUpdateID--;
@@ -1734,7 +1734,7 @@ namespace Barotrauma.Networking
public override void CreateEntityEvent(INetSerializable entity, object[] extraData) public override void CreateEntityEvent(INetSerializable entity, object[] extraData)
{ {
if (!(entity is IClientSerializable)) throw new InvalidCastException("Entity is not IClientSerializable"); if (!(entity is IClientSerializable)) throw new InvalidCastException("entity is not IClientSerializable");
entityEventManager.CreateEvent(entity as IClientSerializable, extraData); entityEventManager.CreateEvent(entity as IClientSerializable, extraData);
} }
@@ -2003,8 +2003,7 @@ namespace Barotrauma.Networking
msg.Write(true); msg.WritePadBits(); msg.Write(true); msg.WritePadBits();
msg.Write(savePath); msg.Write(savePath);
msg.Write(mapSeed); msg.Write(mapSeed);
msg.Write(sub.Name); msg.Write(sub.FilePath);
msg.Write(sub.MD5Hash.Hash);
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered); client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
@@ -2242,25 +2241,13 @@ namespace Barotrauma.Networking
if (EndVoteCount > 0) if (EndVoteCount > 0)
{ {
if (EndVoteTickBox.Visible) string endVoteText = TextManager.Get("EndRoundVotes")
{ .Replace("[y]", EndVoteCount.ToString())
EndVoteTickBox.Text = .Replace("[n]", (EndVoteMax - EndVoteCount).ToString());
(EndVoteTickBox.UserData as string) + " " + EndVoteCount + "/" + EndVoteMax; GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 10.0f - GUI.SmallFont.MeasureString(endVoteText).X, 40),
} endVoteText,
else Color.White,
{ font: GUI.SmallFont);
string endVoteText = TextManager.Get("EndRoundVotes")
.Replace("[votes]", EndVoteCount.ToString())
.Replace("[max]", EndVoteMax.ToString());
GUI.DrawString(spriteBatch, EndVoteTickBox.Rect.Center.ToVector2() - GUI.SmallFont.MeasureString(endVoteText) / 2,
endVoteText,
Color.White,
font: GUI.SmallFont);
}
}
else
{
EndVoteTickBox.Text = EndVoteTickBox.UserData as string;
} }
if (respawnManager != null) if (respawnManager != null)
@@ -1,5 +1,4 @@
using Lidgren.Network; using Lidgren.Network;
using Microsoft.Xna.Framework;
using OpenTK.Audio.OpenAL; using OpenTK.Audio.OpenAL;
using System; using System;
using System.Linq; using System.Linq;
@@ -27,11 +26,6 @@ namespace Barotrauma.Networking
get; get;
private set; private set;
} }
public float Gain
{
get { return GameMain.Config?.MicrophoneVolume ?? 1.0f; }
}
public DateTime LastEnqueueAudio; public DateTime LastEnqueueAudio;
@@ -75,7 +69,6 @@ namespace Barotrauma.Networking
{ {
if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound")) if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound"))
{ {
GUI.SettingsMenuOpen = false;
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("VoipCaptureDeviceNotFound")) new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("VoipCaptureDeviceNotFound"))
{ {
UserData = "capturedevicenotfound" UserData = "capturedevicenotfound"
@@ -167,9 +160,8 @@ namespace Barotrauma.Networking
double maxAmplitude = 0.0f; double maxAmplitude = 0.0f;
for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++) for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++)
{ {
uncompressedBuffer[i] = (short)MathHelper.Clamp((uncompressedBuffer[i] * Gain), -short.MaxValue, short.MaxValue); double sampleVal = (double)uncompressedBuffer[i] / (double)short.MaxValue;
double sampleVal = uncompressedBuffer[i] / (double)short.MaxValue; maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
} }
double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0); double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0);
@@ -150,10 +150,10 @@ namespace Barotrauma
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
Vector2 newPosition = SimPosition; Vector2 newPosition = SimPosition;
float? newRotation = null; float newRotation = Rotation;
bool awake = body.Awake; bool awake = body.Awake;
Vector2 newVelocity = LinearVelocity; Vector2 newVelocity = LinearVelocity;
float? newAngularVelocity = null; float newAngularVelocity = AngularVelocity;
newPosition = new Vector2( newPosition = new Vector2(
msg.ReadFloat(), msg.ReadFloat(),
@@ -179,12 +179,12 @@ namespace Barotrauma
msg.ReadPadBits(); msg.ReadPadBits();
if (!MathUtils.IsValid(newPosition) || if (!MathUtils.IsValid(newPosition) ||
!MathUtils.IsValid(newRotation) ||
!MathUtils.IsValid(newVelocity) || !MathUtils.IsValid(newVelocity) ||
(newRotation.HasValue && !MathUtils.IsValid(newRotation.Value)) || !MathUtils.IsValid(newAngularVelocity))
(newAngularVelocity.HasValue && !MathUtils.IsValid(newAngularVelocity.Value)))
{ {
string errorMsg = "Received invalid position data for \"" + parentDebugName string errorMsg = "Received invalid position data for \"" + parentDebugName
+ "\" (position: " + newPosition + ", rotation: " + (newRotation ?? 0) + ", velocity: " + newVelocity + ", angular velocity: " + (newAngularVelocity ?? 0) + ")"; + "\" (position: " + newPosition + ", rotation: " + newRotation + ", velocity: " + newVelocity + ", angular velocity: " + newAngularVelocity + ")";
#if DEBUG #if DEBUG
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
#endif #endif
@@ -33,12 +33,12 @@ namespace Barotrauma
private readonly bool isMultiplayer; private readonly bool isMultiplayer;
public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<Submarine> submarines, IEnumerable<string> saveFiles = null) public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<string> saveFiles=null)
{ {
this.isMultiplayer = isMultiplayer; this.isMultiplayer = isMultiplayer;
this.newGameContainer = newGameContainer; this.newGameContainer = newGameContainer;
this.loadGameContainer = loadGameContainer; this.loadGameContainer = loadGameContainer;
var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -60,7 +60,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), leftColumn.RectTransform), TextManager.Get("SelectedSub") + ":", textAlignment: Alignment.BottomLeft); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), leftColumn.RectTransform), TextManager.Get("SelectedSub") + ":", textAlignment: Alignment.BottomLeft);
subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform)); subList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.65f), leftColumn.RectTransform));
UpdateSubList(submarines); UpdateSubList();
// New game right side // New game right side
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("SaveName") + ":", textAlignment: Alignment.BottomLeft); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("SaveName") + ":", textAlignment: Alignment.BottomLeft);
@@ -87,8 +87,9 @@ namespace Barotrauma
return false; return false;
} }
if (!(subList.SelectedData is Submarine selectedSub)) { return false; } Submarine selectedSub = subList.SelectedData as Submarine;
if (selectedSub == null) return false;
if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
{ {
((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f; ((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f;
@@ -195,9 +196,9 @@ namespace Barotrauma
public void UpdateSubList(IEnumerable<Submarine> submarines) public void UpdateSubList(IEnumerable<Submarine> submarines)
{ {
#if DEBUG #if DEBUG
var subsToShow = submarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus)); var subsToShow = Submarine.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus));
#else #else
var subsToShow = submarines; var subsToShow = Submarine.SavedSubmarines;
#endif #endif
subList.ClearChildren(); subList.ClearChildren();
@@ -262,6 +263,11 @@ namespace Barotrauma
saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer); saveFiles = SaveUtil.GetSaveFiles(isMultiplayer ? SaveUtil.SaveType.Multiplayer : SaveUtil.SaveType.Singleplayer);
} }
saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft))
{
OnSelected = SelectSaveFile
};
saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft)) saveList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f), loadGameContainer.RectTransform, Anchor.CenterLeft))
{ {
OnSelected = SelectSaveFile OnSelected = SelectSaveFile
@@ -269,9 +275,7 @@ namespace Barotrauma
foreach (string saveFile in saveFiles) foreach (string saveFile in saveFiles)
{ {
string fileName = saveFile; XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
string subName = "";
string saveTime = "";
var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform), style: "ListBoxElement") var saveFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), saveList.Content.RectTransform), style: "ListBoxElement")
{ {
UserData = saveFile UserData = saveFile
@@ -279,38 +283,25 @@ namespace Barotrauma
var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform), var nameText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform),
text: Path.GetFileNameWithoutExtension(saveFile)); text: Path.GetFileNameWithoutExtension(saveFile));
if (doc?.Root == null)
{
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.Color = Color.Red;
continue;
}
if (!isMultiplayer) string submarineName = doc.Root.GetAttributeString("submarine", "");
{
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFile);
if (doc?.Root == null)
{
DebugConsole.ThrowError("Error loading save file \"" + saveFile + "\". The file may be corrupted.");
nameText.Color = Color.Red;
continue;
}
subName = doc.Root.GetAttributeString("submarine", "");
saveTime = doc.Root.GetAttributeString("savetime", "");
}
else
{
string[] splitSaveFile = saveFile.Split(';');
saveFrame.UserData = splitSaveFile[0];
fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]);
if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; }
if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; }
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), saveFrame.RectTransform, Anchor.BottomLeft),
text: subName, font: GUI.SmallFont) text: submarineName, font: GUI.SmallFont)
{ {
UserData = fileName UserData = saveFile
}; };
string saveTime = doc.Root.GetAttributeString("savetime", "");
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), saveFrame.RectTransform),
text: saveTime, textAlignment: Alignment.Right, font: GUI.SmallFont) text: saveTime, textAlignment: Alignment.Right, font: GUI.SmallFont)
{ {
UserData = fileName UserData = saveFile
}; };
} }
@@ -459,12 +459,6 @@ namespace Barotrauma
OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; }, OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return true; },
Enabled = true Enabled = true
}; };
if (GameMain.Client != null)
{
startButton.Visible = !GameMain.Client.GameStarted &&
(GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign));
}
} }
OnLocationSelected?.Invoke(location, connection); OnLocationSelected?.Invoke(location, connection);
@@ -508,13 +502,7 @@ namespace Barotrauma
CanBeFocused = false CanBeFocused = false
}; };
if (startButton != null) if (startButton != null) { startButton.Enabled = true; }
{
startButton.Enabled = true;
startButton.Visible = GameMain.Client == null ||
GameMain.Client.HasPermission(Networking.ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign);
}
} }
private void CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIListBox listBox, int width) private void CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIListBox listBox, int width)
@@ -118,19 +118,6 @@ namespace Barotrauma
instance = this; instance = this;
} }
private void Reset()
{
AnimParams.ForEach(a => a.Reset(true));
RagdollParams.Reset(true);
RagdollParams.ClearHistory();
CurrentAnimation.ClearHistory();
if (!character.Removed)
{
character.Remove();
}
character = null;
}
public override void Deselect() public override void Deselect()
{ {
base.Deselect(); base.Deselect();
@@ -141,7 +128,15 @@ namespace Barotrauma
isEndlessRunner = false; isEndlessRunner = false;
if (character != null) if (character != null)
{ {
Reset(); AnimParams.ForEach(a => a.Reset(true));
RagdollParams.Reset(true);
RagdollParams.ClearHistory();
CurrentAnimation.ClearHistory();
if (!character.Removed)
{
character.Remove();
}
character = null;
} }
GameMain.World.ProcessChanges(); GameMain.World.ProcessChanges();
} }
@@ -398,12 +393,6 @@ namespace Barotrauma
} }
if (!isFreezed) if (!isFreezed)
{ {
if (character.AnimController.Invalid)
{
Reset();
SpawnCharacter(currentCharacterConfig);
}
Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position); Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position);
Submarine.MainSub.Update((float)deltaTime); Submarine.MainSub.Update((float)deltaTime);
@@ -1226,7 +1215,7 @@ namespace Barotrauma
Cam.Position = character.WorldPosition; Cam.Position = character.WorldPosition;
} }
private bool CreateCharacter(string name, string mainFolder, bool isHumanoid, params object[] ragdollConfig) private bool CreateCharacter(string name, bool isHumanoid, params object[] ragdollConfig)
{ {
var contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault(); var contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault();
if (contentPackage == null) if (contentPackage == null)
@@ -1245,16 +1234,17 @@ namespace Barotrauma
#endif #endif
string speciesName = name; string speciesName = name;
string mainFolder = $"Content/Characters/{speciesName}";
// Config file // Config file
string configFilePath = Path.Combine(mainFolder, $"{speciesName}.xml").Replace(@"\", @"/"); string configFilePath = $"{mainFolder}/{speciesName}.xml";
if (ContentPackage.GetFilesOfType(GameMain.SelectedPackages, ContentType.Character).None(path => path.Contains(speciesName))) if (ContentPackage.GetFilesOfType(GameMain.SelectedPackages, ContentType.Character).None(path => path.Contains(speciesName)))
{ {
// Create the config file // Create the config file
XElement mainElement = new XElement("Character", XElement mainElement = new XElement("Character",
new XAttribute("name", speciesName), new XAttribute("name", speciesName),
new XAttribute("humanoid", isHumanoid), new XAttribute("humanoid", isHumanoid),
new XElement("ragdolls", new XAttribute("folder", Path.Combine(mainFolder, $"Ragdolls/").Replace(@"\", @"/"))), new XElement("ragdolls"),
new XElement("animations", new XAttribute("folder", Path.Combine(mainFolder, $"Animations/").Replace(@"\", @"/"))), new XElement("animations"),
new XElement("health"), new XElement("health"),
new XElement("ai")); new XElement("ai"));
XDocument doc = new XDocument(mainElement); XDocument doc = new XDocument(mainElement);
@@ -1269,13 +1259,13 @@ namespace Barotrauma
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
} }
// Ragdoll // Ragdoll
string ragdollFolder = RagdollParams.GetFolder(speciesName); string ragdollFolder = RagdollParams.GetDefaultFolder(speciesName);
string ragdollPath = RagdollParams.GetDefaultFile(speciesName); string ragdollPath = RagdollParams.GetDefaultFile(speciesName);
RagdollParams ragdollParams = isHumanoid RagdollParams ragdollParams = isHumanoid
? RagdollParams.CreateDefault<HumanRagdollParams>(ragdollPath, speciesName, ragdollConfig) ? RagdollParams.CreateDefault<HumanRagdollParams>(ragdollPath, speciesName, ragdollConfig)
: RagdollParams.CreateDefault<FishRagdollParams>(ragdollPath, speciesName, ragdollConfig) as RagdollParams; : RagdollParams.CreateDefault<FishRagdollParams>(ragdollPath, speciesName, ragdollConfig) as RagdollParams;
// Animations // Animations
string animFolder = AnimationParams.GetFolder(speciesName); string animFolder = AnimationParams.GetDefaultFolder(speciesName);
foreach (AnimationType animType in Enum.GetValues(typeof(AnimationType))) foreach (AnimationType animType in Enum.GetValues(typeof(AnimationType)))
{ {
if (animType != AnimationType.NotDefined) if (animType != AnimationType.NotDefined)
@@ -4662,7 +4652,7 @@ namespace Barotrauma
LimbXElements.Values, LimbXElements.Values,
JointXElements JointXElements
}; };
if (CharacterEditorScreen.instance.CreateCharacter(Name, Path.GetDirectoryName(XMLPath), IsHumanoid, ragdollParams)) if (CharacterEditorScreen.instance.CreateCharacter(Name, IsHumanoid, ragdollParams))
{ {
GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", Name), Color.Green, font: GUI.Font); GUI.AddMessage(GetCharacterEditorTranslation("CharacterCreated").Replace("[name]", Name), Color.Green, font: GUI.Font);
} }
@@ -282,7 +282,7 @@ namespace Barotrauma
menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize)); menuTabs[(int)Tab.LoadGame] = new GUIFrame(new RectTransform(relativeSize, GUI.Canvas, anchor, pivot, minSize, maxSize));
var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center), style: null); var paddedLoadGame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), menuTabs[(int)Tab.LoadGame].RectTransform, Anchor.Center), style: null);
campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame, Submarine.SavedSubmarines) campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame)
{ {
LoadGame = LoadGame, LoadGame = LoadGame,
StartNewGame = StartGame StartNewGame = StartGame
@@ -336,7 +336,7 @@ namespace Barotrauma
UpdateTutorialList(); UpdateTutorialList();
campaignSetupUI.UpdateSubList(Submarine.SavedSubmarines); campaignSetupUI.UpdateSubList();
ResetButtonStates(null); ResetButtonStates(null);
@@ -632,13 +632,6 @@ namespace Barotrauma
Visible = false Visible = false
}; };
campaignViewButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), rightInfoColumn.RectTransform),
TextManager.Get("CampaignView"), style: "GUIButtonLarge")
{
OnClicked = (btn, obj) => { ToggleCampaignView(true); return true; },
Visible = false
};
StartButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.1f), infoFrameContent.RectTransform, Anchor.BottomRight), StartButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.1f), infoFrameContent.RectTransform, Anchor.BottomRight),
TextManager.Get("StartGameButton"), style: "GUIButtonLarge") TextManager.Get("StartGameButton"), style: "GUIButtonLarge")
{ {
@@ -651,6 +644,13 @@ namespace Barotrauma
}; };
clientHiddenElements.Add(StartButton); clientHiddenElements.Add(StartButton);
campaignViewButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.1f), infoFrameContent.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.06f) },
TextManager.Get("CampaignView"), style: "GUIButtonLarge")
{
OnClicked = (btn, obj) => { ToggleCampaignView(true); return true; },
Visible = false
};
spectateButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.1f), infoFrameContent.RectTransform, Anchor.BottomRight), spectateButton = new GUIButton(new RectTransform(new Vector2(0.3f, 0.1f), infoFrameContent.RectTransform, Anchor.BottomRight),
TextManager.Get("SpectateButton"), style: "GUIButtonLarge"); TextManager.Get("SpectateButton"), style: "GUIButtonLarge");
} }
@@ -726,12 +726,6 @@ namespace Barotrauma
spectateButton.Visible = GameMain.Client.GameStarted; spectateButton.Visible = GameMain.Client.GameStarted;
ReadyToStartBox.Visible = !GameMain.Client.GameStarted; ReadyToStartBox.Visible = !GameMain.Client.GameStarted;
ReadyToStartBox.Selected = false; ReadyToStartBox.Selected = false;
if (campaignUI?.StartButton != null)
{
campaignUI.StartButton.Visible = !GameMain.Client.GameStarted &&
(GameMain.Client.HasPermission(ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign));
}
GameMain.Client.SetReadyToStart(ReadyToStartBox); GameMain.Client.SetReadyToStart(ReadyToStartBox);
} }
else else
@@ -850,13 +844,6 @@ namespace Barotrauma
GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog); GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
GameMain.Client.EndRoundButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound); GameMain.Client.EndRoundButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound);
if (campaignUI?.StartButton != null)
{
campaignUI.StartButton.Visible = !GameMain.Client.GameStarted &&
(GameMain.Client.HasPermission(ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign));
}
} }
public void ShowSpectateButton() public void ShowSpectateButton()
@@ -866,21 +853,18 @@ namespace Barotrauma
spectateButton.Enabled = true; spectateButton.Enabled = true;
} }
public void SetCampaignCharacterInfo(CharacterInfo newCampaignCharacterInfo) public void SetCampaignCharacterInfo(CharacterInfo characterInfo)
{ {
if (newCampaignCharacterInfo != null) if (CampaignCharacterDiscarded) return;
campaignCharacterInfo = characterInfo;
if (campaignCharacterInfo != null)
{ {
if (CampaignCharacterDiscarded) { return; } UpdatePlayerFrame(campaignCharacterInfo, false);
if (campaignCharacterInfo != newCampaignCharacterInfo)
{
campaignCharacterInfo = newCampaignCharacterInfo;
UpdatePlayerFrame(campaignCharacterInfo, false);
}
} }
else if (campaignCharacterInfo != null) else
{ {
campaignCharacterInfo = null; UpdatePlayerFrame(null, true);
UpdatePlayerFrame(campaignCharacterInfo, false);
} }
} }
@@ -1207,7 +1191,7 @@ namespace Barotrauma
if (sub.HasTag(SubmarineTag.Shuttle)) if (sub.HasTag(SubmarineTag.Shuttle))
{ {
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.1f, 0.0f) }, new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight),
TextManager.Get("Shuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) TextManager.Get("Shuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{ {
TextColor = subTextBlock.TextColor * 0.8f, TextColor = subTextBlock.TextColor * 0.8f,
@@ -716,7 +716,7 @@ namespace Barotrauma
catch (PingException ex) catch (PingException ex)
{ {
string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message); string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message);
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg); GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
#if DEBUG #if DEBUG
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
#endif #endif
@@ -564,9 +564,6 @@ namespace Barotrauma
MapEntityPrefab.Selected = null; MapEntityPrefab.Selected = null;
saveFrame = null;
loadFrame = null;
MapEntity.DeselectAll(); MapEntity.DeselectAll();
MapEntity.SelectionGroups.Clear(); MapEntity.SelectionGroups.Clear();
@@ -2043,10 +2040,6 @@ namespace Barotrauma
dummyCharacter.SelectedConstruction = null; dummyCharacter.SelectedConstruction = null;
} }
} }
else if (MapEntity.SelectedList.Count == 1)
{
(MapEntity.SelectedList[0] as Item)?.UpdateHUD(cam, dummyCharacter, (float)deltaTime);
}
CharacterHUD.Update((float)deltaTime, dummyCharacter, cam); CharacterHUD.Update((float)deltaTime, dummyCharacter, cam);
} }
@@ -26,10 +26,6 @@ namespace Barotrauma
private int subDivX, subDivY; private int subDivX, subDivY;
private static Effect effect; private static Effect effect;
public static Effect Effect
{
get { return effect; }
}
private Point spritePos; private Point spritePos;
private Point spriteSize; private Point spriteSize;
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.8.9.7")] [assembly: AssemblyVersion("0.8.9.6")]
[assembly: AssemblyFileVersion("0.8.9.7")] [assembly: AssemblyFileVersion("0.8.9.6")]
@@ -4,6 +4,11 @@ namespace Barotrauma
{ {
partial class CharacterInfo partial class CharacterInfo
{ {
partial void SpawnInventoryItemProjSpecific(Item item)
{
Entity.Spawner.CreateNetworkEvent(item, false);
}
public void ServerWrite(NetBuffer msg) public void ServerWrite(NetBuffer msg)
{ {
msg.Write(ID); msg.Write(ID);
@@ -81,12 +81,11 @@ namespace Barotrauma
//dequeue messages //dequeue messages
lock (queuedMessages) lock (queuedMessages)
{ {
if (queuedMessages.Count > 0) if (queuedMessages.Count>0)
{ {
int inputLines = Math.Max((int)Math.Ceiling(input.Length / (float)Console.WindowWidth), 1);
Console.CursorLeft = 0; Console.CursorLeft = 0;
Console.Write(new string(' ', consoleWidth)); Console.Write(new string(' ', consoleWidth));
Console.CursorTop -= inputLines; Console.CursorLeft = 0; Console.CursorTop--; Console.CursorLeft = 0;
while (queuedMessages.Count > 0) while (queuedMessages.Count > 0)
{ {
ColoredText msg = queuedMessages.Dequeue(); ColoredText msg = queuedMessages.Dequeue();
@@ -178,11 +177,11 @@ namespace Barotrauma
private static void RewriteInputToCommandLine(string input) private static void RewriteInputToCommandLine(string input)
{ {
int consoleWidth = Math.Max(Console.WindowWidth, 5); Console.WriteLine(""); Console.CursorTop--;
int inputLines = Math.Max((int)Math.Ceiling(input.Length / (float)consoleWidth), 1); int consoleWidth = Console.WindowWidth;
int cursorLine = Math.Max((int)Math.Ceiling((input.Length + 1) / (float)consoleWidth), 1); if (consoleWidth < 5) consoleWidth = 5;
int consoleHeight = Console.WindowHeight;
Console.WriteLine(""); Console.CursorTop -= inputLines; if (consoleHeight < 5) consoleHeight = 5;
string ln = input.Length > 0 ? AutoComplete(input, 0) : ""; string ln = input.Length > 0 ? AutoComplete(input, 0) : "";
ln += new string(' ', consoleWidth - (ln.Length % consoleWidth)); ln += new string(' ', consoleWidth - (ln.Length % consoleWidth));
@@ -191,9 +190,9 @@ namespace Barotrauma
Console.Write(ln); Console.Write(ln);
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.White;
Console.CursorLeft = 0; Console.CursorLeft = 0;
Console.CursorTop -= cursorLine; Console.CursorTop--;
Console.Write(input); Console.Write(input);
Console.CursorLeft = input.Length % Console.WindowWidth; Console.CursorLeft = input.Length;
} }
private static void AssignOnClientRequestExecute(string names, Action<Client, Vector2, string[]> onClientRequestExecute) private static void AssignOnClientRequestExecute(string names, Action<Client, Vector2, string[]> onClientRequestExecute)
@@ -1555,9 +1554,9 @@ namespace Barotrauma
})); }));
#if DEBUG #if DEBUG
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) => commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) =>
{ {
/*foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
foreach (ItemComponent component in item.Components) foreach (ItemComponent component in item.Components)
{ {
@@ -1574,18 +1573,16 @@ namespace Barotrauma
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status }); GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status });
} }
} }
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status }); GameMain.Server.CreateEntityEvent(c, new object[] { NetEntityEvent.Type.Status });
}*/
foreach (Hull hull in Hull.hullList)
{
GameMain.Server.CreateEntityEvent(hull);
} }
foreach (Structure wall in Structure.WallList) foreach (Structure wall in Structure.WallList)
{ {
GameMain.Server.CreateEntityEvent(wall); GameMain.Server.CreateEntityEvent(wall);
} }
})); }));
#endif #endif
} }
@@ -11,11 +11,11 @@ namespace Barotrauma
{ {
private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>(); private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
public static void StartNewCampaign(string savePath, string subPath, string seed) public static void StartNewCampaign(string savePath, string subName, string seed)
{ {
if (string.IsNullOrWhiteSpace(savePath)) return; if (string.IsNullOrWhiteSpace(savePath)) return;
GameMain.GameSession = new GameSession(new Submarine(subPath, ""), savePath, GameMain.GameSession = new GameSession(new Submarine(subName, ""), savePath,
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign")); GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign"));
var campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode); var campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
campaign.GenerateMap(seed); campaign.GenerateMap(seed);
@@ -72,27 +72,6 @@ namespace Barotrauma
}); });
} }
public bool AllowedToEndRound(Character interactor)
{
if (interactor == null || Level.Loaded?.StartOutpost == null || Level.Loaded?.EndOutpost == null)
{
return false;
}
if (interactor.Submarine == Level.Loaded.StartOutpost &&
interactor.CanInteractWith(startWatchman))
{
return true;
}
if (interactor.Submarine == Level.Loaded.EndOutpost &&
interactor.CanInteractWith(endWatchman))
{
return true;
}
return false;
}
protected override void WatchmanInteract(Character watchman, Character interactor) protected override void WatchmanInteract(Character watchman, Character interactor)
{ {
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) || if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
@@ -106,7 +85,8 @@ namespace Barotrauma
if (GameMain.Server != null) if (GameMain.Server != null)
{ {
var client = GameMain.Server.ConnectedClients.Find(c => c.Character == interactor); var client = GameMain.Server.ConnectedClients.Find(c => c.Character == interactor);
hasPermissions = client != null; hasPermissions = client != null &&
(client.HasPermission(ClientPermissions.ManageRound) || client.HasPermission(ClientPermissions.ManageCampaign));
CreateDialog(new List<Character> { watchman }, hasPermissions ? "WatchmanInteract" : "WatchmanInteractNotAllowed", 1.0f); CreateDialog(new List<Character> { watchman }, hasPermissions ? "WatchmanInteract" : "WatchmanInteractNotAllowed", 1.0f);
} }
} }
@@ -251,7 +231,6 @@ namespace Barotrauma
} }
lastSaveID++; lastSaveID++;
DebugConsole.Log("Campaign saved, save ID " + lastSaveID);
} }
} }
} }
@@ -1,5 +1,9 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -10,16 +14,5 @@ namespace Barotrauma.Items.Components
//let the clients know the initial deterioration delay //let the clients know the initial deterioration delay
item.CreateServerEvent(this); item.CreateServerEvent(this);
} }
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
if (c.Character == null) return;
StartRepairing(c.Character);
}
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(deteriorationTimer);
}
} }
} }
@@ -335,14 +335,6 @@ namespace Barotrauma
{ {
if (GameMain.Server == null) return; if (GameMain.Server == null) return;
if (!ItemList.Contains(this))
{
string errorMsg = "Attempted to create a network event for an item (" + Name + ") that hasn't been fully initialized yet.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Item.CreateServerEvent:EventForUninitializedItem" + Name + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
int index = components.IndexOf(ic); int index = components.IndexOf(ic);
if (index == -1) return; if (index == -1) return;
+2 -12
View File
@@ -20,16 +20,6 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam) partial void UpdateProjSpecific(float deltaTime, Camera cam)
{ {
if (IdFreed) { return; } if (IdFreed) { return; }
//don't create updates if all clients are very far from the hull
float hullUpdateDistanceSqr = NetConfig.HullUpdateDistance * NetConfig.HullUpdateDistance;
if (!GameMain.Server.ConnectedClients.Any(c =>
c.Character != null &&
Vector2.DistanceSquared(c.Character.WorldPosition, WorldPosition) < hullUpdateDistanceSqr))
{
return;
}
//update client hulls if the amount of water has changed by >10% //update client hulls if the amount of water has changed by >10%
//or if oxygen percentage has changed by 5% //or if oxygen percentage has changed by 5%
if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f || if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f ||
@@ -41,8 +31,8 @@ namespace Barotrauma
GameMain.NetworkMember.CreateEntityEvent(this); GameMain.NetworkMember.CreateEntityEvent(this);
lastSentVolume = waterVolume; lastSentVolume = waterVolume;
lastSentOxygen = OxygenPercentage; lastSentOxygen = OxygenPercentage;
sendUpdateTimer = NetConfig.HullUpdateInterval; sendUpdateTimer = NetworkUpdateInterval;
} }
} }
} }
@@ -46,7 +46,7 @@ namespace Barotrauma.Networking
//when was a specific entity event last sent to the client //when was a specific entity event last sent to the client
// key = event id, value = NetTime.Now when sending // key = event id, value = NetTime.Now when sending
public readonly Dictionary<UInt16, double> EntityEventLastSent = new Dictionary<UInt16, double>(); public readonly Dictionary<UInt16, float> EntityEventLastSent = new Dictionary<UInt16, float>();
//when was a position update for a given entity last sent to the client //when was a position update for a given entity last sent to the client
// key = entity id, value = NetTime.Now when sending // key = entity id, value = NetTime.Now when sending
@@ -187,7 +187,7 @@ namespace Barotrauma.Networking
transfer.WaitTimer -= deltaTime; transfer.WaitTimer -= deltaTime;
if (transfer.WaitTimer > 0.0f) continue; if (transfer.WaitTimer > 0.0f) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel)) continue; if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue;
transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime; transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime;
@@ -202,6 +202,15 @@ namespace Barotrauma.Networking
{ {
message = peer.CreateMessage(); message = peer.CreateMessage();
message.Write((byte)ServerPacketHeader.FILE_TRANSFER); message.Write((byte)ServerPacketHeader.FILE_TRANSFER);
message.Write((byte)FileTransferMessageType.Initiate);
message.Write((byte)transfer.FileType);
message.Write((ushort)chunkLen);
message.Write((ulong)transfer.Data.Length);
message.Write(transfer.FileName);
GameMain.Server.CompressOutgoingMessage(message);
transfer.Connection.SendMessage(message, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
transfer.Status = FileTransferStatus.Sending;
//if the recipient is the owner of the server (= a client running the server from the main exe) //if the recipient is the owner of the server (= a client running the server from the main exe)
//we don't need to send anything, the client can just read the file directly //we don't need to send anything, the client can just read the file directly
@@ -10,7 +10,6 @@ using System.Text;
using System.IO.Compression; using System.IO.Compression;
using System.IO; using System.IO;
using Barotrauma.Steam; using Barotrauma.Steam;
using System.Xml.Linq;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -44,7 +43,6 @@ namespace Barotrauma.Networking
private IRestResponse masterServerResponse; private IRestResponse masterServerResponse;
private bool autoRestartTimerRunning; private bool autoRestartTimerRunning;
private float endRoundTimer;
public VoipServer VoipServer public VoipServer VoipServer
{ {
@@ -407,38 +405,20 @@ namespace Barotrauma.Networking
} }
} }
if (isCrewDead && respawnManager == null)
{
if (endRoundTimer <= 0.0f)
{
SendChatMessage(TextManager.Get("CrewDeadNoRespawns").Replace("[time]", "60"), ChatMessageType.Server);
}
endRoundTimer += deltaTime;
}
else
{
endRoundTimer = 0.0f;
}
//restart if all characters are dead or submarine is at the end of the level //restart if all characters are dead or submarine is at the end of the level
if ((serverSettings.AutoRestart && isCrewDead) if ((serverSettings.AutoRestart && isCrewDead)
|| ||
(serverSettings.EndRoundAtLevelEnd && subAtLevelEnd) (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd))
||
(isCrewDead && respawnManager == null && endRoundTimer >= 60.0f))
{ {
if (serverSettings.AutoRestart && isCrewDead) if (serverSettings.AutoRestart && isCrewDead)
{ {
Log("Ending round (entire crew dead)", ServerLog.MessageType.ServerMessage); Log("Ending round (entire crew dead)", ServerLog.MessageType.ServerMessage);
} }
else if (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd) else
{ {
Log("Ending round (submarine reached the end of the level)", ServerLog.MessageType.ServerMessage); Log("Ending round (submarine reached the end of the level)", ServerLog.MessageType.ServerMessage);
} }
else
{
Log("Ending round (no living players left and respawning is not enabled during this round)", ServerLog.MessageType.ServerMessage);
}
EndGame(); EndGame();
return; return;
} }
@@ -717,24 +697,13 @@ namespace Barotrauma.Networking
string savePath = inc.ReadString(); string savePath = inc.ReadString();
string seed = inc.ReadString(); string seed = inc.ReadString();
string subName = inc.ReadString(); string subName = inc.ReadString();
string subHash = inc.ReadString();
var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash); if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.StartNewCampaign(savePath, subName, seed);
}
if (matchingSub == null)
{
SendDirectChatMessage(
TextManager.Get("CampaignStartFailedSubNotFound").Replace("[subname]", subName),
connectedClient, ChatMessageType.MessageBox);
}
else
{
if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.StartNewCampaign(savePath, matchingSub.FilePath, seed);
}
}
else else
{ {
string saveName = inc.ReadString(); string saveName = inc.ReadString();
if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.LoadCampaign(saveName); if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.LoadCampaign(saveName);
} }
break; break;
@@ -765,64 +734,6 @@ namespace Barotrauma.Networking
fileSender.ReadFileRequest(inc, connectedClient); fileSender.ReadFileRequest(inc, connectedClient);
} }
break; break;
case ClientPacketHeader.ERROR:
HandleClientError(inc, connectedClient);
break;
}
}
private void HandleClientError(NetIncomingMessage inc, Client c)
{
string errorStr = "Unhandled error report";
ClientNetError error = (ClientNetError)inc.ReadByte();
int levelEqualityCheckVal = inc.ReadInt32();
switch (error)
{
case ClientNetError.MISSING_EVENT:
UInt16 expectedID = inc.ReadUInt16();
UInt16 receivedID = inc.ReadUInt16();
errorStr = "Expecting event id " + expectedID.ToString() + ", received " + receivedID.ToString();
break;
case ClientNetError.MISSING_ENTITY:
UInt16 eventID = inc.ReadUInt16();
UInt16 entityID = inc.ReadUInt16();
Entity entity = Entity.FindEntityByID(entityID);
if (entity == null)
{
errorStr = "Received an update for an entity that doesn't exist (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
}
else if (entity is Character character)
{
errorStr = "Missing character " + character.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
}
else if (entity is Item item)
{
errorStr = "Missing item " + item.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
}
else
{
errorStr = "Missing entity " + entity.ToString() + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ").";
}
break;
}
if (Level.Loaded != null && levelEqualityCheckVal != Level.Loaded.EqualityCheckVal)
{
errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
}
Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error);
GameAnalyticsManager.AddErrorEventOnce("GameServer.HandleClientError:LevelsDontMatch" + error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorStr);
if (c.Connection == OwnerConnection)
{
SendDirectChatMessage(errorStr, c, ChatMessageType.MessageBox);
EndGame();
}
else
{
KickClient(c, errorStr);
} }
} }
@@ -880,8 +791,8 @@ namespace Barotrauma.Networking
//(the server started a new campaign and the client isn't aware of it yet?) //(the server started a new campaign and the client isn't aware of it yet?)
if (campaign.CampaignID != campaignID) if (campaign.CampaignID != campaignID)
{ {
c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1); c.LastRecvCampaignSave = 0;
c.LastRecvCampaignUpdate = (ushort)(campaign.LastUpdateID - 1); c.LastRecvCampaignUpdate = 0;
} }
} }
} }
@@ -1035,17 +946,7 @@ namespace Barotrauma.Networking
return; return;
} }
//clients are allowed to end the round by talking with the watchman in multiplayer if (!sender.HasPermission(command))
//campaign even if they don't have the special permission
if (command == ClientPermissions.ManageRound && inc.PeekBoolean() &&
GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign)
{
if (!mpCampaign.AllowedToEndRound(sender.Character) && !sender.HasPermission(command))
{
return;
}
}
else if (!sender.HasPermission(command))
{ {
Log("Client \"" + sender.Name + "\" sent a server command \"" + command + "\". Permission denied.", ServerLog.MessageType.ServerMessage); Log("Client \"" + sender.Name + "\" sent a server command \"" + command + "\". Permission denied.", ServerLog.MessageType.ServerMessage);
return; return;
@@ -1126,22 +1027,9 @@ namespace Barotrauma.Networking
UInt16 modeIndex = inc.ReadUInt16(); UInt16 modeIndex = inc.ReadUInt16();
if (GameMain.NetLobbyScreen.GameModes[modeIndex].Identifier.ToLowerInvariant() == "multiplayercampaign") if (GameMain.NetLobbyScreen.GameModes[modeIndex].Identifier.ToLowerInvariant() == "multiplayercampaign")
{ {
string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer);
for (int i = 0; i < saveFiles.Length; i++)
{
XDocument doc = SaveUtil.LoadGameSessionDoc(saveFiles[i]);
if (doc?.Root != null)
{
saveFiles[i] =
string.Join(";",
saveFiles[i].Replace(';', ' '),
doc.Root.GetAttributeString("submarine", ""),
doc.Root.GetAttributeString("savetime", ""));
}
}
NetOutgoingMessage msg = server.CreateMessage(); NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO); msg.Write((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
string[] saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer);
msg.Write((UInt16)saveFiles.Count()); msg.Write((UInt16)saveFiles.Count());
foreach (string saveFile in saveFiles) foreach (string saveFile in saveFiles)
{ {
@@ -1226,7 +1114,6 @@ namespace Barotrauma.Networking
ClientWriteLobby(c); ClientWriteLobby(c);
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign &&
GameMain.NetLobbyScreen.SelectedMode == campaign.Preset &&
NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave)) NetIdUtils.IdMoreRecent(campaign.LastSaveID, c.LastRecvCampaignSave))
{ {
//already sent an up-to-date campaign save //already sent an up-to-date campaign save
@@ -1361,6 +1248,10 @@ namespace Barotrauma.Networking
WriteClientList(c, outmsg); WriteClientList(c, outmsg);
clientListBytes = outmsg.LengthBytes - clientListBytes; clientListBytes = outmsg.LengthBytes - clientListBytes;
int eventManagerBytes = outmsg.LengthBytes;
entityEventManager.Write(c, outmsg, out List<NetEntityEvent> sentEvents);
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
int chatMessageBytes = outmsg.LengthBytes; int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(outmsg, c); WriteChatMessages(outmsg, c);
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes; chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
@@ -1409,55 +1300,25 @@ namespace Barotrauma.Networking
errorMsg += errorMsg +=
" Client list size: " + clientListBytes + " bytes\n" + " Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" + " Chat message size: " + chatMessageBytes + " bytes\n" +
" Event size: " + eventManagerBytes + " bytes\n" +
" Position update size: " + positionUpdateBytes + " bytes\n\n"; " Position update size: " + positionUpdateBytes + " bytes\n\n";
if (sentEvents != null && sentEvents.Count > 0)
{
errorMsg += "Sent events: \n";
foreach (var entityEvent in sentEvents)
{
errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n";
}
}
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
} }
CompressOutgoingMessage(outmsg); CompressOutgoingMessage(outmsg);
server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable); server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable);
//---------------------------------------------------------------------------
for (int i = 0; i < NetConfig.MaxEventPacketsPerUpdate; i++)
{
outmsg = server.CreateMessage();
outmsg.Write((byte)ServerPacketHeader.UPDATE_INGAME);
outmsg.Write((float)NetTime.Now);
int eventManagerBytes = outmsg.LengthBytes;
entityEventManager.Write(c, outmsg, out List<NetEntityEvent> sentEvents);
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
if (sentEvents.Count == 0)
{
break;
}
outmsg.Write((byte)ServerNetObject.END_OF_MESSAGE);
if (outmsg.LengthBytes > NetPeerConfiguration.MaximumTransmissionUnit)
{
string errorMsg = "Maximum packet size exceeded (" + outmsg.LengthBytes + " > " + NetPeerConfiguration.MaximumTransmissionUnit + ")\n";
errorMsg +=
" Event size: " + eventManagerBytes + " bytes\n";
if (sentEvents != null && sentEvents.Count > 0)
{
errorMsg += "Sent events: \n";
foreach (var entityEvent in sentEvents)
{
errorMsg += " - " + (entityEvent.Entity?.ToString() ?? "null") + "\n";
}
}
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame2:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
CompressOutgoingMessage(outmsg);
server.SendMessage(outmsg, c.Connection, NetDeliveryMethod.Unreliable);
}
} }
private void WriteClientList(Client c, NetOutgoingMessage outmsg) private void WriteClientList(Client c, NetOutgoingMessage outmsg)
@@ -1544,8 +1405,7 @@ namespace Barotrauma.Networking
} }
var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; var campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (campaign != null && campaign.Preset == GameMain.NetLobbyScreen.SelectedMode && if (campaign != null && NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate))
NetIdUtils.IdMoreRecent(campaign.LastUpdateID, c.LastRecvCampaignUpdate))
{ {
outmsg.Write(true); outmsg.Write(true);
outmsg.WritePadBits(); outmsg.WritePadBits();
@@ -1702,23 +1562,9 @@ namespace Barotrauma.Networking
Rand.SetSyncedSeed(roundStartSeed); Rand.SetSyncedSeed(roundStartSeed);
int teamCount = 1; int teamCount = 1;
MultiPlayerCampaign campaign = selectedMode == GameMain.GameSession?.GameMode.Preset ? MultiPlayerCampaign campaign = GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
GameMain.GameSession?.GameMode as MultiPlayerCampaign : null; GameMain.GameSession?.GameMode as MultiPlayerCampaign : null;
if (campaign != null && campaign.Map == null)
{
initiatedStartGame = false;
startGameCoroutine = null;
string errorMsg = "Starting the round failed. Campaign was still active, but the map has been disposed. Try selecting another game mode.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.StartGame:InvalidCampaignState", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
if (OwnerConnection != null)
{
SendDirectChatMessage(errorMsg, connectedClients.Find(c => c.Connection == OwnerConnection), ChatMessageType.Error);
}
yield return CoroutineStatus.Failure;
}
//don't instantiate a new gamesession if we're playing a campaign //don't instantiate a new gamesession if we're playing a campaign
if (campaign == null || GameMain.GameSession == null) if (campaign == null || GameMain.GameSession == null)
{ {
@@ -1743,17 +1589,15 @@ namespace Barotrauma.Networking
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
campaign.AssignClientCharacterInfos(connectedClients); campaign.AssignClientCharacterInfos(connectedClients);
Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage);
Log("Submarine: " + GameMain.GameSession.Submarine.Name, ServerLog.MessageType.ServerMessage);
Log("Level seed: " + campaign.Map.SelectedConnection.Level.Seed, ServerLog.MessageType.ServerMessage);
} }
else else
{ {
GameMain.GameSession.StartRound(GameMain.NetLobbyScreen.LevelSeed, serverSettings.SelectedLevelDifficulty, teamCount > 1); GameMain.GameSession.StartRound(GameMain.NetLobbyScreen.LevelSeed, serverSettings.SelectedLevelDifficulty, teamCount > 1);
Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage); }
Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage);
Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, ServerLog.MessageType.ServerMessage); Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage);
} Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage);
Log("Level seed: " + GameMain.NetLobbyScreen.LevelSeed, ServerLog.MessageType.ServerMessage);
bool missionAllowRespawn = campaign == null && bool missionAllowRespawn = campaign == null &&
(!(GameMain.GameSession.GameMode is MissionMode) || (!(GameMain.GameSession.GameMode is MissionMode) ||
@@ -1915,7 +1759,6 @@ namespace Barotrauma.Networking
msg.Write(seed); msg.Write(seed);
msg.Write(GameMain.GameSession.Level.Seed); msg.Write(GameMain.GameSession.Level.Seed);
msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
msg.Write(serverSettings.SelectedLevelDifficulty); msg.Write(serverSettings.SelectedLevelDifficulty);
msg.Write((byte)GameMain.Config.LosMode); msg.Write((byte)GameMain.Config.LosMode);
@@ -1977,8 +1820,6 @@ namespace Barotrauma.Networking
Mission mission = GameMain.GameSession.Mission; Mission mission = GameMain.GameSession.Mission;
GameMain.GameSession.GameMode.End(endMessage); GameMain.GameSession.GameMode.End(endMessage);
endRoundTimer = 0.0f;
if (serverSettings.AutoRestart) if (serverSettings.AutoRestart)
{ {
@@ -2056,7 +1897,14 @@ namespace Barotrauma.Networking
public override void AddChatMessage(ChatMessage message) public override void AddChatMessage(ChatMessage message)
{ {
if (string.IsNullOrEmpty(message.Text)) { return; } if (string.IsNullOrEmpty(message.Text)) { return; }
Log(message.TextWithSender, ServerLog.MessageType.Chat); if (message.Sender != null)
{
Log($"{message.Sender}: {message.Text}", ServerLog.MessageType.Chat);
}
else
{
Log($"{message.Text}", ServerLog.MessageType.Chat);
}
base.AddChatMessage(message); base.AddChatMessage(message);
} }
@@ -2519,8 +2367,6 @@ namespace Barotrauma.Networking
public void SendVoteStatus(List<Client> recipients) public void SendVoteStatus(List<Client> recipients)
{ {
if (!recipients.Any()) { return; }
NetOutgoingMessage msg = server.CreateMessage(); NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ServerNetObject.VOTE); msg.Write((byte)ServerNetObject.VOTE);
@@ -2573,10 +2419,7 @@ namespace Barotrauma.Networking
recipients.Add(otherClient.Connection); recipients.Add(otherClient.Connection);
} }
} }
if (recipients.Any()) server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
{
server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
}
serverSettings.SaveClientPermissions(); serverSettings.SaveClientPermissions();
} }
@@ -2610,15 +2453,13 @@ namespace Barotrauma.Networking
public void UpdateCheatsEnabled() public void UpdateCheatsEnabled()
{ {
if (!connectedClients.Any()) { return; }
var msg = server.CreateMessage(); var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED); msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED);
msg.Write(DebugConsole.CheatsEnabled); msg.Write(DebugConsole.CheatsEnabled);
msg.WritePadBits(); msg.WritePadBits();
CompressOutgoingMessage(msg); CompressOutgoingMessage(msg);
server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0); server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
} }
@@ -158,15 +158,15 @@ namespace Barotrauma.Networking
} }
//kick connected client if status becomes invalid (e.g. VAC banned, not connected to steam) //kick connected client if status becomes invalid (e.g. VAC banned, not connected to steam)
/*if (status != Facepunch.Steamworks.ServerAuth.Status.OK && GameMain.Config.RequireSteamAuthentication) if (status != Facepunch.Steamworks.ServerAuth.Status.OK && GameMain.Config.RequireSteamAuthentication)
{ {
var connectedClient = connectedClients.Find(c => c.SteamID == ownerID); var connectedClient = connectedClients.Find(c => c.SteamID == ownerID);
if (connectedClient != null) if (connectedClient != null)
{ {
Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage); Log("Disconnecting client " + connectedClient.Name + " (Steam ID: " + steamID + "). Steam authentication no longer valid (" + status + ").", ServerLog.MessageType.ServerMessage);
KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid~[status]={status.ToString()}"); KickClient(connectedClient, $"DisconnectMessage.SteamAuthNoLongerValid_[status]={status.ToString()}");
} }
}*/ }
} }
private bool IsServerOwner(NetIncomingMessage inc, NetConnection senderConnection) private bool IsServerOwner(NetIncomingMessage inc, NetConnection senderConnection)
@@ -342,7 +342,7 @@ namespace Barotrauma.Networking
if (clVersion != GameMain.Version.ToString()) if (clVersion != GameMain.Version.ToString())
{ {
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion, DisconnectUnauthClient(inc, unauthClient, DisconnectReason.InvalidVersion,
$"DisconnectMessage.InvalidVersion~[version]={GameMain.Version.ToString()}~[clientversion]={clVersion}"); $"DisconnectMessage.InvalidVersion_[version]={GameMain.Version.ToString()}_[clientversion]={clVersion}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", ServerLog.MessageType.Error); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", ServerLog.MessageType.Error);
DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", Color.Red); DebugConsole.NewMessage(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (wrong game version)", Color.Red);
@@ -368,7 +368,7 @@ namespace Barotrauma.Networking
if (missingPackages.Count == 1) if (missingPackages.Count == 1)
{ {
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}"); DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackage_[missingcontentpackage]={GetPackageStr(missingPackages[0])}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content package " + GetPackageStr(missingPackages[0]) + ")", ServerLog.MessageType.Error);
return; return;
} }
@@ -376,7 +376,7 @@ namespace Barotrauma.Networking
{ {
List<string> packageStrs = new List<string>(); List<string> packageStrs = new List<string>();
missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp))); missingPackages.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}"); DisconnectUnauthClient(inc, unauthClient, DisconnectReason.MissingContentPackage, $"DisconnectMessage.MissingContentPackages_[missingcontentpackages]={string.Join(", ", packageStrs)}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (missing content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return; return;
} }
@@ -399,7 +399,7 @@ namespace Barotrauma.Networking
if (incompatiblePackages.Count == 1) if (incompatiblePackages.Count == 1)
{ {
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage, DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackage~[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}"); $"DisconnectMessage.IncompatibleContentPackage_[incompatiblecontentpackage]={GetPackageStr2(incompatiblePackages[0])}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content package " + GetPackageStr2(incompatiblePackages[0]) + ")", ServerLog.MessageType.Error);
return; return;
} }
@@ -408,7 +408,7 @@ namespace Barotrauma.Networking
List<string> packageStrs = new List<string>(); List<string> packageStrs = new List<string>();
incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp))); incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp)));
DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage, DisconnectUnauthClient(inc, unauthClient, DisconnectReason.IncompatibleContentPackage,
$"DisconnectMessage.IncompatibleContentPackages~[incompatiblecontentpackages]={string.Join(", ", packageStrs)}"); $"DisconnectMessage.IncompatibleContentPackages_[incompatiblecontentpackages]={string.Join(", ", packageStrs)}");
Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error); Log(clName + " (" + inc.SenderConnection.RemoteEndPoint.Address.ToString() + ") couldn't join the server (incompatible content packages " + string.Join(", ", packageStrs) + ")", ServerLog.MessageType.Error);
return; return;
} }
@@ -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() + "/ " + TextManager.GetServerMessage(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)
{ {
@@ -1,5 +1,4 @@
using Barotrauma.Extensions; using Lidgren.Network;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -209,6 +208,7 @@ namespace Barotrauma.Networking
lastSentToAll = owner.LastRecvEntityEventID; lastSentToAll = owner.LastRecvEntityEventID;
} }
} }
inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) lastSentToAll = c.LastRecvEntityEventID; }); inGameClients.ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.LastRecvEntityEventID)) lastSentToAll = c.LastRecvEntityEventID; });
clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); }); clients.Where(c => c.NeedsMidRoundSync).ForEach(c => { if (NetIdUtils.IdMoreRecent(lastSentToAll, c.FirstNewEventID)) lastSentToAll = (ushort)(c.FirstNewEventID - 1); });
@@ -228,8 +228,8 @@ namespace Barotrauma.Networking
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event " GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event "
+ (c.LastRecvEntityEventID + 1).ToString() + + (c.LastRecvEntityEventID + 1).ToString() +
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" + " (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" +
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error); " Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.ServerMessage);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncOldEvent"); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesync");
} }
); );
} }
@@ -242,8 +242,8 @@ namespace Barotrauma.Networking
toKick.ForEach(c => toKick.ForEach(c =>
{ {
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red);
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.Error); GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.ServerMessage);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncRemovedEvent"); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesync");
}); });
} }
} }
@@ -251,7 +251,7 @@ namespace Barotrauma.Networking
var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut); var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
foreach (Client timedOutClient in timedOutClients) foreach (Client timedOutClient in timedOutClients)
{ {
GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error); GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.ServerMessage);
GameMain.Server.DisconnectClient(timedOutClient, "", "ServerMessage.SyncTimeout"); GameMain.Server.DisconnectClient(timedOutClient, "", "ServerMessage.SyncTimeout");
} }
@@ -305,7 +305,19 @@ namespace Barotrauma.Networking
} }
//too many events for one packet //too many events for one packet
if (eventsToSync.Count > 200) if (eventsToSync.Count > MaxEventsPerWrite)
{
if (eventsToSync.Count > MaxEventsPerWrite * 3 && !client.NeedsMidRoundSync)
{
Color color = eventsToSync.Count > MaxEventsPerWrite * 20 ? Color.Red : Color.Orange;
if (eventsToSync.Count < MaxEventsPerWrite * 5) { color = Color.Yellow; }
DebugConsole.NewMessage("WARNING: event count very high: " + eventsToSync.Count + "/" + MaxEventsPerWrite, color);
}
eventsToSync.RemoveRange(MaxEventsPerWrite, eventsToSync.Count - MaxEventsPerWrite);
}
foreach (NetEntityEvent entityEvent in eventsToSync)
{ {
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync) if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync)
{ {
@@ -334,11 +346,19 @@ namespace Barotrauma.Networking
if (client.NeedsMidRoundSync) if (client.NeedsMidRoundSync)
{ {
msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL); msg.Write((byte)ServerNetObject.ENTITY_EVENT_INITIAL);
//how many (unique) events the clients had missed before joining
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
//ID of the first event sent after the client joined
//(after the client has been synced they'll switch their lastReceivedID
//to the one before this, and the eventmanagers will start to function "normally")
client.FirstNewEventID = events.Count == 0 ? (UInt16)0 : events[events.Count - 1].ID;
msg.Write(client.UnreceivedEntityEventCount); msg.Write(client.UnreceivedEntityEventCount);
msg.Write(client.FirstNewEventID); msg.Write(client.FirstNewEventID);
Write(msg, eventsToSync, out sentEvents, client); Write(msg, eventsToSync, client);
} }
else else
{ {
@@ -351,6 +371,7 @@ namespace Barotrauma.Networking
(entityEvent as ServerEntityEvent).Sent = true; (entityEvent as ServerEntityEvent).Sent = true;
client.EntityEventLastSent[entityEvent.ID] = NetTime.Now; client.EntityEventLastSent[entityEvent.ID] = NetTime.Now;
} }
sentEvents = eventsToSync;
} }
/// <summary> /// <summary>
@@ -374,10 +395,10 @@ namespace Barotrauma.Networking
for (int i = startIndex; i < eventList.Count; i++) for (int i = startIndex; i < eventList.Count; i++)
{ {
//find the first event that hasn't been sent in roundtriptime or at all //find the first event that hasn't been sent in 1.5 * roundtriptime or at all
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out double lastSent); client.EntityEventLastSent.TryGetValue(eventList[i].ID, out float lastSent);
float minInterval = Math.Max(client.Connection.AverageRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2); float minInterval = Math.Max(client.Connection.AverageRoundtripTime * 1.5f, (float)server.UpdateInterval.TotalSeconds * 2);
if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f)) if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f))
{ {
@@ -403,7 +424,7 @@ namespace Barotrauma.Networking
} }
else else
{ {
double midRoundSyncTimeOut = uniqueEvents.Count / 100 * server.UpdateInterval.TotalSeconds; double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds;
midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f); midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f);
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count; client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;
@@ -78,7 +78,6 @@ namespace Barotrauma.Networking
bool senderSpectating = sender.Character == null || sender.Character.IsDead; bool senderSpectating = sender.Character == null || sender.Character.IsDead;
//spectators cannot speak with in-game players or vice versa //spectators cannot speak with in-game players or vice versa
//TODO: allow spectators to hear the voice chat if close enough to the speaker?
if (recipientSpectating != senderSpectating) { return false; } if (recipientSpectating != senderSpectating) { return false; }
//both spectating, no need to do radio/distance checks //both spectating, no need to do radio/distance checks
@@ -116,8 +116,8 @@ namespace Barotrauma
msg.Write(AllowEndVoting); msg.Write(AllowEndVoting);
if (AllowEndVoting) if (AllowEndVoting)
{ {
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound))); msg.Write((byte)GameMain.Server.ConnectedClients.Count(v => v.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned)); msg.Write((byte)GameMain.Server.ConnectedClients.Count);
} }
msg.Write(AllowVoteKick); msg.Write(AllowVoteKick);
@@ -56,7 +56,6 @@
<Character file="Content/Characters/Husk/Husk.xml" /> <Character file="Content/Characters/Husk/Husk.xml" />
<Character file="Content/Characters/Humanhusk/Humanhusk.xml" /> <Character file="Content/Characters/Humanhusk/Humanhusk.xml" />
<Character file="Content/Characters/Legacyhusk/Legacyhusk.xml" /> <Character file="Content/Characters/Legacyhusk/Legacyhusk.xml" />
<Character file="Content/Characters/Legacycharybdis/Legacycharybdis.xml" />
<Character file="Content/Characters/Legacycrawler/Legacycrawler.xml" /> <Character file="Content/Characters/Legacycrawler/Legacycrawler.xml" />
<Character file="Content/Characters/Legacymoloch/Legacymoloch.xml" /> <Character file="Content/Characters/Legacymoloch/Legacymoloch.xml" />
<Character file="Content/Characters/Mudraptor/Mudraptor.xml" /> <Character file="Content/Characters/Mudraptor/Mudraptor.xml" />
@@ -71,12 +70,9 @@
<Outpost file="Content/Map/Outposts/Outpost.sub" /> <Outpost file="Content/Map/Outposts/Outpost.sub" />
<Submarine file="Submarines/Orca.sub" /> <Submarine file="Submarines/Orca.sub" />
<Submarine file="Submarines/Typhon.sub" /> <Submarine file="Submarines/Typhon.sub" />
<Submarine file="Submarines/Selkie.sub" /> <Submarine file="Submarines/Muikku.sub" />
<Submarine file="Submarines/Bunyip.sub" /> <Submarine file="Submarines/Bunyip.sub" />
<Submarine file="Submarines/Humpback.sub" /> <Submarine file="Submarines/Humpback.sub" />
<Submarine file="Submarines/Dugong.sub" />
<Submarine file="Submarines/Remora.sub" />
<Submarine file="Submarines/RemoraDrone.sub" />
<Text file="Content/Texts/EnglishVanilla.xml" /> <Text file="Content/Texts/EnglishVanilla.xml" />
<UIStyle file="Content/UI/style.xml"/> <UIStyle file="Content/UI/style.xml"/>
<Afflictions file="Content/Afflictions.xml"/> <Afflictions file="Content/Afflictions.xml"/>
@@ -10,7 +10,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Carrier\Animations\" /> <Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Carrier\Animations\" />
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\" /> <Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Animations\" />
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\Animations\" /> <Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\Animations\" />
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycrawler\" /> <Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycrawler\" />
<Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Endworm\Animations\" /> <Folder Include="$(MSBuildThisFileDirectory)Content\Characters\Endworm\Animations\" />
@@ -45,27 +45,12 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Carrier\Ragdolls\CarrierDefaultRagdoll.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Carrier\Ragdolls\CarrierDefaultRagdoll.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Animations\CharybdisSwimFast.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Animations\CharybdisSwimSlow.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Charybdis.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Charybdis.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Charybdis.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Ragdolls\CharybdisDefaultRagdoll.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Ragdolls\CharybdisDefaultRagdoll.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\Legacycharybdis.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\Ragdolls\LegacycharybdisDefaultRagdoll.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\Coelanth.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\Coelanth.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -912,13 +897,13 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Carrier\Animations\CarrierSwimSlow.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Carrier\Animations\CarrierSwimSlow.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\charybdis.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\charybdis.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\Animations\LegacycharybdisSwimFast.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Animations\CharybdisSwimFast.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\Animations\LegacycharybdisSwimSlow.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\Animations\CharybdisSwimSlow.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\Animations\CoelanthSwimFast.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\Animations\CoelanthSwimFast.xml">
@@ -1658,12 +1643,6 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\lightcone.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Lights\lightcone.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\divinghelmetlight.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\divinghelmetvisorlight.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\penumbra.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Lights\penumbra.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -2371,7 +2350,7 @@
<None Include="$(MSBuildThisFileDirectory)Content\Characters\Mudraptor\MUDRAPTOR_idle3.ogg"> <None Include="$(MSBuildThisFileDirectory)Content\Characters\Mudraptor\MUDRAPTOR_idle3.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Content\Characters\Legacycharybdis\charybdisattack.ogg"> <None Include="$(MSBuildThisFileDirectory)Content\Characters\Charybdis\charybdisattack.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\attack1.ogg"> <None Include="$(MSBuildThisFileDirectory)Content\Characters\Coelanth\attack1.ogg">
@@ -3129,7 +3108,7 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Muikku.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Muikku.sub">
<CopyToOutputDirectory>Never</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Orca.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Orca.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -3144,13 +3123,8 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Selkie.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Typhon.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Typhon.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -110,7 +110,7 @@ namespace Barotrauma
SonarLabel = element.GetAttributeString("sonarlabel", ""); SonarLabel = element.GetAttributeString("sonarlabel", "");
} }
public AITarget(Entity e, float sightRange = 3000, float soundRange = 0) public AITarget(Entity e)
{ {
Entity = e; Entity = e;
SightRange = sightRange; SightRange = sightRange;
@@ -26,6 +26,7 @@ namespace Barotrauma
} }
} }
public class TargetingPriority public class TargetingPriority
{ {
public string TargetTag; public string TargetTag;
@@ -99,9 +100,6 @@ namespace Barotrauma
private readonly float aggressiongreed; private readonly float aggressiongreed;
private readonly float aggressionhurt; private readonly float aggressionhurt;
// TODO: expose?
private readonly float priorityFearIncreasement = 2;
private readonly float memoryFadeTime = 0.5f;
public bool AttackHumans public bool AttackHumans
{ {
@@ -251,7 +249,8 @@ namespace Barotrauma
public override void SelectTarget(AITarget target) public override void SelectTarget(AITarget target)
{ {
SelectedAiTarget = target; SelectedAiTarget = target;
selectedTargetMemory = GetTargetMemory(target); selectedTargetMemory = FindTargetMemory(target);
targetValue = 100.0f; targetValue = 100.0f;
} }
@@ -288,7 +287,8 @@ namespace Barotrauma
} }
else else
{ {
UpdateTargets(Character, out TargetingPriority targetingPriority); TargetingPriority targetingPriority = null;
UpdateTargets(Character, out targetingPriority);
updateTargetsTimer = UpdateTargetsInterval; updateTargetsTimer = UpdateTargetsInterval;
if (SelectedAiTarget == null) if (SelectedAiTarget == null)
@@ -395,44 +395,13 @@ namespace Barotrauma
State = AIState.Idle; State = AIState.Idle;
return; return;
} }
else if (selectedTargetMemory != null)
Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
SteeringManager.SteeringWander();
if (Character.CurrentHull == null)
{ {
selectedTargetMemory.Priority += deltaTime * priorityFearIncreasement;
}
if (Character.CurrentHull != null)
{
// Seek exit, if inside
if (SteeringManager is IndoorsSteeringManager indoorSteering && escapePoint == Vector2.Zero)
{
foreach (Gap gap in Gap.GapList)
{
if (gap.Submarine != Character.Submarine) { continue; }
if (gap.Open < 1 || gap.IsRoomToRoom) { continue; }
var path = indoorSteering.PathFinder.FindPath(Character.SimPosition, gap.SimPosition);
if (!path.Unreachable)
{
if (escapePoint != Vector2.Zero)
{
// Ignore the gap if it's further away than the previously assigned escape point
if (Vector2.DistanceSquared(Character.SimPosition, gap.SimPosition) > Vector2.DistanceSquared(Character.SimPosition, escapePoint)) { continue; }
}
escapePoint = gap.SimPosition;
}
}
}
}
if (escapePoint != Vector2.Zero && Vector2.DistanceSquared(Character.SimPosition, escapePoint) > 1)
{
SteeringManager.SteeringSeek(escapePoint);
}
else
{
// If outside or near enough the escapePoint, steer away
escapePoint = Vector2.Zero;
Vector2 escapeDir = Vector2.Normalize(WorldPosition - SelectedAiTarget.WorldPosition);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
SteeringManager.SteeringWander();
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); SteeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f);
} }
} }
@@ -449,8 +418,14 @@ namespace Barotrauma
return; return;
} }
Vector2 attackWorldPos = SelectedAiTarget.WorldPosition; selectedTargetMemory.Priority -= deltaTime * 0.1f;
Vector2 attackSimPos = SelectedAiTarget.SimPosition;
Vector2 attackSimPosition = Character.Submarine == null ? ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition) : SelectedAiTarget.SimPosition;
if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine != null && Character.Submarine != SelectedAiTarget.Entity.Submarine)
{
attackSimPosition = ConvertUnits.ToSimUnits(SelectedAiTarget.WorldPosition - Character.Submarine.Position);
}
if (SelectedAiTarget.Entity is Item item) if (SelectedAiTarget.Entity is Item item)
{ {
@@ -466,27 +441,22 @@ namespace Barotrauma
} }
} }
if (raycastTimer > 0.0) if (wallTarget != null)
{ {
raycastTimer -= deltaTime; attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position);
} if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null)
else
{
if (!IsProperlyLatched)
{ {
UpdateWallTarget(); attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
} }
raycastTimer = RaycastInterval;
} }
else if (SelectedAiTarget.Entity is Character c)
if (SelectedAiTarget.Entity is Character c)
{ {
//target the closest limb if the target is a character //target the closest limb if the target is a character
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.WorldPosition, WorldPosition) * 10.0f; float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f;
foreach (Limb limb in c.AnimController.Limbs) foreach (Limb limb in ((Character)SelectedAiTarget.Entity).AnimController.Limbs)
{ {
if (limb == null) continue; if (limb == null) continue;
float dist = Vector2.DistanceSquared(limb.WorldPosition, WorldPosition) / Math.Max(limb.AttackPriority, 0.1f); float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition) / Math.Max(limb.AttackPriority, 0.1f);
if (dist < closestDist) if (dist < closestDist)
{ {
closestDist = dist; closestDist = dist;
@@ -518,12 +488,7 @@ namespace Barotrauma
} }
} }
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater) if (raycastTimer > 0.0)
{
Character.AnimController.TargetDir = Character.WorldPosition.X < attackWorldPos.X ? Direction.Right : Direction.Left;
}
if (aggressiveBoarding)
{ {
//targeting a wall section that can be passed through -> steer manually through the hole //targeting a wall section that can be passed through -> steer manually through the hole
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex)) if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex))
@@ -582,6 +547,74 @@ namespace Barotrauma
} }
} }
bool canAttack = true;
if (IsCoolDownRunning)
{
UpdateWallTarget();
raycastTimer = RaycastInterval;
}
if (aggressiveBoarding)
{
//targeting a wall section that can be passed through -> steer manually through the hole
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex))
{
WallSection section = wallTarget.Structure.GetSection(wallTarget.SectionIndex);
Hull targetHull = section.gap?.FlowTargetHull;
if (targetHull != null && !section.gap.IsRoomToRoom)
{
Vector2 targetPos = wallTarget.Structure.SectionPosition(wallTarget.SectionIndex, true);
if (wallTarget.Structure.IsHorizontal)
{
targetPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2;
}
else
{
targetPos.X = targetHull.WorldRect.Center.X;
}
latchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
if (steeringManager is IndoorsSteeringManager)
{
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetPos - Character.WorldPosition));
}
else
{
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetPos));
}
return;
}
}
else if (SelectedAiTarget.Entity is Item)
{
var door = ((Item)SelectedAiTarget.Entity).GetComponent<Door>();
//steer through the door manually if it's open or broken
if (door?.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom && (door.IsOpen || door.Item.Condition <= 0.0f))
{
var velocity = Vector2.Normalize(door.LinkedGap.FlowTargetHull.WorldPosition - Character.WorldPosition);
if (door.LinkedGap.IsHorizontal)
{
if (Character.WorldPosition.Y < door.Item.WorldRect.Y && Character.WorldPosition.Y > door.Item.WorldRect.Y - door.Item.Rect.Height)
{
velocity.Y = 0;
steeringManager.SteeringManual(deltaTime, velocity);
return;
}
}
else
{
if (Character.WorldPosition.X < door.Item.WorldRect.X && Character.WorldPosition.X > door.Item.WorldRect.Right)
{
velocity.X = 0;
steeringManager.SteeringManual(deltaTime, velocity);
return;
}
}
}
}
}
bool canAttack = true; bool canAttack = true;
if (IsCoolDownRunning) if (IsCoolDownRunning)
{ {
@@ -598,7 +631,7 @@ namespace Barotrauma
} }
else else
{ {
UpdateFallBack(attackWorldPos, deltaTime); UpdateFallBack(attackSimPosition, deltaTime);
return; return;
} }
} }
@@ -606,14 +639,14 @@ namespace Barotrauma
{ {
if (attackingLimb.attack.SecondaryCoolDownTimer <= 0) if (attackingLimb.attack.SecondaryCoolDownTimer <= 0)
{ {
// Don't allow attacking when the attack target has just changed. // Don't allow attacking when the attack target has changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget) if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
{ {
canAttack = false; canAttack = false;
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack) if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
{ {
// Fall back if cannot attack. // Fall back if cannot attack.
UpdateFallBack(attackWorldPos, deltaTime); UpdateFallBack(attackSimPosition, deltaTime);
return; return;
} }
attackingLimb = null; attackingLimb = null;
@@ -622,7 +655,7 @@ namespace Barotrauma
{ {
// If the secondary cooldown is defined and expired, check if we can switch the attack // If the secondary cooldown is defined and expired, check if we can switch the attack
var previousLimb = attackingLimb; var previousLimb = attackingLimb;
var newLimb = GetAttackLimb(attackWorldPos, previousLimb); var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
if (newLimb != null) if (newLimb != null)
{ {
attackingLimb = newLimb; attackingLimb = newLimb;
@@ -636,7 +669,7 @@ namespace Barotrauma
} }
else else
{ {
UpdateFallBack(attackWorldPos, deltaTime); UpdateFallBack(attackSimPosition, deltaTime);
return; return;
} }
} }
@@ -651,15 +684,15 @@ namespace Barotrauma
break; break;
case AIBehaviorAfterAttack.FallBack: case AIBehaviorAfterAttack.FallBack:
default: default:
UpdateFallBack(attackWorldPos, deltaTime); UpdateFallBack(attackSimPosition, deltaTime);
return; return;
} }
} }
if (attackingLimb == null || _previousAiTarget != SelectedAiTarget) if (attackingLimb == null)
{ {
attackingLimb = GetAttackLimb(attackWorldPos); attackingLimb = GetAttackLimb(attackSimPosition);
} }
if (canAttack) if (canAttack)
{ {
@@ -669,39 +702,24 @@ namespace Barotrauma
if (canAttack) if (canAttack)
{ {
// Check that we can reach the target // Check that we can reach the target
distance = Vector2.Distance(attackingLimb.WorldPosition, attackWorldPos); distance = ConvertUnits.ToDisplayUnits(Vector2.Distance(attackingLimb.SimPosition, attackSimPosition));
canAttack = distance < attackingLimb.attack.Range; canAttack = distance < attackingLimb.attack.Range;
} }
// If the attacking limb is a hand or claw, for example, using it as the steering limb can end in the result where the character circles around the target. For example the Hammerhead steering with the claws when it should use the torso. Limb steeringLimb = Character.AnimController.MainLimb;
// If we always use the main limb, this causes the character to seek the target with it's torso/head, when it should not. For example Mudraptor steering with it's belly, when it should use it's head.
// So let's use the one that's closer to the attacking limb.
Limb steeringLimb;
var torso = Character.AnimController.GetLimb(LimbType.Torso);
var head = Character.AnimController.GetLimb(LimbType.Head);
if (attackingLimb == null)
{
steeringLimb = head ?? torso;
}
else
{
if (head != null && torso != null)
{
steeringLimb = Vector2.DistanceSquared(attackingLimb.SimPosition, head.SimPosition) < Vector2.DistanceSquared(attackingLimb.SimPosition, torso.SimPosition) ? head : torso;
}
else
{
steeringLimb = head ?? torso;
}
}
if (steeringLimb != null) if (steeringLimb != null)
{ {
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition; Vector2 steeringVector = attackSimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement Vector2 targetingVector = Vector2.Normalize(steeringVector) * attackingLimb.attack.Range;
Vector2 steerPos = attackSimPos + offset; // Offset the position a bit so that we don't overshoot the movement.
SteeringManager.SteeringSeek(steerPos, 10); Vector2 steerPos = attackSimPosition + targetingVector;
steeringManager.SteeringSeek(steerPos, 10);
if (Character.CurrentHull == null)
{
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
}
if (SteeringManager is IndoorsSteeringManager indoorsSteering) if (steeringManager is IndoorsSteeringManager indoorsSteering)
{ {
if (indoorsSteering.CurrentPath != null && !indoorsSteering.IsPathDirty) if (indoorsSteering.CurrentPath != null && !indoorsSteering.IsPathDirty)
{ {
@@ -716,7 +734,7 @@ namespace Barotrauma
} }
else if (indoorsSteering.CurrentPath.Finished) else if (indoorsSteering.CurrentPath.Finished)
{ {
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition)); steeringManager.SteeringManual(deltaTime, Vector2.Normalize(steeringVector));
} }
else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null) else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null)
{ {
@@ -730,42 +748,40 @@ namespace Barotrauma
} }
} }
} }
else if (Character.CurrentHull == null)
{
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
}
} }
if (canAttack) if (canAttack)
{ {
UpdateLimbAttack(deltaTime, attackingLimb, attackSimPos, distance); UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition, distance);
} }
} }
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime) private Limb GetAttackLimb(Vector2 attackSimPosition, Limb ignoredLimb = null)
{ {
Hull targetHull = section.gap?.FlowTargetHull; AttackContext currentContext = Character.GetAttackContext();
if (targetHull != null) var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
{ var limbs = Character.AnimController.Limbs
if (wall.IsHorizontal) .Where(l =>
{ l != ignoredLimb &&
targetWorldPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2; l.attack != null &&
} !l.IsSevered &&
else !l.IsStuck &&
{ l.attack.IsValidContext(currentContext) &&
targetWorldPos.X = targetHull.WorldRect.Center.X; l.attack.IsValidTarget(target) &&
} l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character)))
latchOntoAI?.DeattachFromBody(); .OrderByDescending(l => l.attack.Priority)
Character.AnimController.ReleaseStuckLimbs(); .ThenBy(l => ConvertUnits.ToDisplayUnits(Vector2.Distance(l.SimPosition, attackSimPosition)));
if (steeringManager is IndoorsSteeringManager) // TODO: priority should probably not override the distance -> use values instead of booleans
{ return limbs.FirstOrDefault();
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition)); }
}
else private void UpdateWallTarget()
{ {
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos)); wallTarget = null;
}
return true; if (Character.AnimController.CurrentHull != null)
{
return;
} }
return false; return false;
} }
@@ -794,66 +810,84 @@ namespace Barotrauma
wallTarget = null; wallTarget = null;
//check if there's a wall between the target and the Character //check if there's a wall between the target and the Character
Vector2 rayStart = SimPosition; Vector2 rayStart = Character.SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition; Vector2 rayEnd = SelectedAiTarget.SimPosition;
bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null;
if (offset) if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
{ {
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition; rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
} }
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true); Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
if (Submarine.LastPickedFraction == 1.0f || closestBody == null) if (Submarine.LastPickedFraction == 1.0f || closestBody == null)
{ {
return; return;
} }
if (closestBody.UserData is Structure wall && wall.Submarine != null) Structure wall = closestBody.UserData as Structure;
if (wall?.Submarine == null)
{
return;
/*if (selectedAiTarget.Entity.Submarine != null)
{
wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition), selectedAiTarget.Entity.Submarine);
latchOntoAI?.SetAttachTarget(closestBody, selectedAiTarget.Entity.Submarine, Submarine.LastPickedPosition);
}*/
//if (selectedAiTarget.Entity.Submarine != null && Character.Submarine == null) wallAttackPos += ConvertUnits.ToSimUnits(selectedAiTarget.Entity.Submarine.Position);
}
else
{ {
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition)); int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
int passableHoleCount = GetMinimumPassableHoleCount();
float sectionDamage = wall.SectionDamage(sectionIndex); float sectionDamage = wall.SectionDamage(sectionIndex);
for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++) for (int i = sectionIndex - 2; i <= sectionIndex + 2; i++)
{ {
if (wall.SectionBodyDisabled(i)) if (wall.SectionBodyDisabled(i))
{ {
if (aggressiveBoarding && CanPassThroughHole(wall, i)) if (aggressiveBoarding && CanPassThroughHole(wall, i)) //aggressive boarders always target holes they can pass through
{ {
//aggressive boarders always target holes they can pass through
sectionIndex = i; sectionIndex = i;
break; break;
} }
else else //otherwise ignore and keep breaking other sections
{ {
//otherwise ignore and keep breaking other sections
continue; continue;
} }
} }
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i; if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
} }
Vector2 sectionPos = wall.SectionPosition(sectionIndex); Vector2 sectionPos = ConvertUnits.ToSimUnits(wall.SectionPosition(sectionIndex));
Vector2 attachTargetNormal; Vector2 attachTargetNormal;
if (wall.IsHorizontal) if (wall.IsHorizontal)
{ {
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y)); attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y));
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y; sectionPos.Y += ConvertUnits.ToSimUnits((wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2) * attachTargetNormal.Y;
} }
else else
{ {
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f); attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f);
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X; sectionPos.X += ConvertUnits.ToSimUnits((wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2) * attachTargetNormal.X;
} }
latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, ConvertUnits.ToSimUnits(sectionPos), attachTargetNormal); wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(sectionPos), wall, sectionIndex);
wallTarget = new WallTarget(sectionPos, wall, sectionIndex); latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, sectionPos, attachTargetNormal);
} }
} }
public override void OnAttacked(Character attacker, AttackResult attackResult) public override void OnAttacked(Character attacker, AttackResult attackResult)
{ {
updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f); updateTargetsTimer = Math.Min(updateTargetsTimer, 0.1f);
// Reduce the cooldown so that the character can react
foreach (var limb in Character.AnimController.Limbs)
{
if (limb.attack != null)
{
limb.attack.CoolDownTimer *= 0.1f;
// secondary cooldown?
}
}
if (attackResult.Damage > 0.0f && attackWhenProvoked) if (attackResult.Damage > 0.0f && attackWhenProvoked)
{ {
@@ -868,60 +902,37 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs(); Character.AnimController.ReleaseStuckLimbs();
if (attacker == null || attacker.AiTarget == null) return; if (attacker == null || attacker.AiTarget == null) return;
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget); AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget);
targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt; targetMemory.Priority += GetRelativeDamage(attackResult.Damage, Character.Vitality) * aggressionhurt;
// Reduce the cooldown so that the character can react
// Only allow to react once. Otherwise would attack the target with only a fraction of cooldown
if (SelectedAiTarget != attacker.AiTarget)
{
foreach (var limb in Character.AnimController.Limbs)
{
if (limb.attack != null)
{
limb.attack.CoolDownTimer *= 0.1f;
}
}
}
} }
// 10 dmg, 100 health -> 0.1 // 10 dmg, 100 health -> 0.1
private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f); private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f);
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackSimPos, float distance = -1) private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition, float distance = -1)
{ {
if (SelectedAiTarget == null) { return; } var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
if (wallTarget != null) if (damageTarget == null) return;
float prevHealth = damageTarget.Health;
if (limb.UpdateAttack(deltaTime, attackPosition, damageTarget, out AttackResult attackResult, distance))
{ {
// If the selected target is not the wall target, make the wall target the selected target. if (damageTarget.Health > 0)
var aiTarget = wallTarget.Structure.AiTarget;
if (aiTarget != null && SelectedAiTarget != aiTarget)
{ {
SelectTarget(aiTarget); // Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed;
} }
} }
if (SelectedAiTarget.Entity is IDamageable damageTarget)
if (!limb.attack.IsRunning)
{ {
float prevHealth = damageTarget.Health; wallTarget = null;
if (limb.UpdateAttack(deltaTime, attackSimPos, damageTarget, out AttackResult attackResult, distance))
{
if (damageTarget.Health > 0)
{
// Managed to hit a living/non-destroyed target. Increase the priority more if the target is low in health -> dies easily/soon
selectedTargetMemory.Priority += GetRelativeDamage(attackResult.Damage, damageTarget.Health) * aggressiongreed;
}
else
{
selectedTargetMemory.Priority = 0;
}
}
} }
} }
private void UpdateFallBack(Vector2 attackWorldPos, float deltaTime) private void UpdateFallBack(Vector2 attackPosition, float deltaTime)
{ {
Vector2 attackVector = attackWorldPos - WorldPosition; float dist = Vector2.Distance(attackPosition, Character.SimPosition);
float dist = attackVector.Length();
float desiredDist = colliderSize * 2.0f; float desiredDist = colliderSize * 2.0f;
if (dist < desiredDist) if (dist < desiredDist)
{ {
@@ -929,6 +940,7 @@ namespace Barotrauma
if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY; if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY;
steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f))); steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f)));
} }
steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f); steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f);
} }
@@ -979,20 +991,15 @@ namespace Barotrauma
//goes through all the AItargets, evaluates how preferable it is to attack the target, //goes through all the AItargets, evaluates how preferable it is to attack the target,
//whether the Character can see/hear the target and chooses the most preferable target within //whether the Character can see/hear the target and chooses the most preferable target within
//sight/hearing range //sight/hearing range
public AITarget UpdateTargets(Character character, out TargetingPriority priority) public void UpdateTargets(Character character, out TargetingPriority targetingPriority)
{ {
if (IsProperlyLatched) targetingPriority = null;
{ SelectedAiTarget = null;
// If attached to a valid target, just keep the target.
// Priority not used in this case.
priority = null;
return SelectedAiTarget;
}
AITarget newTarget = null;
priority = null;
selectedTargetMemory = null; selectedTargetMemory = null;
targetValue = 0.0f; targetValue = 0.0f;
UpdateTargetMemories();
foreach (AITarget target in AITarget.List) foreach (AITarget target in AITarget.List)
{ {
if (!target.Enabled) continue; if (!target.Enabled) continue;
@@ -1001,57 +1008,29 @@ namespace Barotrauma
continue; continue;
} }
float valueModifier = 1.0f;
float dist = 0.0f;
Character targetCharacter = target.Entity as Character; Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the Character itself //ignore the aitarget if it is the Character itself
if (targetCharacter == character) continue; if (targetCharacter == character) continue;
float valueModifier = 1;
string targetingTag = null; string targetingTag = null;
if (targetCharacter != null) if (targetCharacter != null)
{ {
if (targetCharacter.IsDead) if (targetCharacter.Submarine != null && Character.Submarine == null)
{
//target inside, AI outside -> we'll be attacking a wall between the characters so use the priority for attacking rooms
targetingTag = "room";
}
else if (targetCharacter.IsDead)
{ {
targetingTag = "dead"; targetingTag = "dead";
if (targetCharacter.Submarine != Character.Submarine)
{
// In a different sub or the target is outside when we are inside or vice versa -> Ignore the target
continue;
}
else if (targetCharacter.CurrentHull != Character.CurrentHull)
{
// In the same sub, halve the priority, if not in the same hull.
valueModifier = 0.5f;
}
} }
else if (targetCharacter.AIController is EnemyAIController enemy) else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
{ {
if (enemy.combatStrength > combatStrength) targetingTag = targetCharacter.SpeciesName.ToLowerInvariant();
{
targetingTag = "stronger";
}
else if (enemy.combatStrength < combatStrength)
{
targetingTag = "weaker";
}
if (State == AIState.Escape && targetingTag == "stronger")
{
// Frightened
valueModifier = 2;
}
else
{
if (targetCharacter.Submarine != Character.Submarine)
{
// In a different sub or the target is outside when we are inside or vice versa -> Ignore the target
continue;
}
else if (targetCharacter.CurrentHull != Character.CurrentHull)
{
// In the same sub, halve the priority, if not in the same hull.
valueModifier = 0.5f;
}
}
} }
else if (targetCharacter.Submarine != null && Character.Submarine == null) else if (targetCharacter.Submarine != null && Character.Submarine == null)
{ {
@@ -1068,6 +1047,42 @@ namespace Barotrauma
//skip the target if it's a room and the character is already inside a sub //skip the target if it's a room and the character is already inside a sub
if (character.CurrentHull != null && target.Entity is Hull) continue; if (character.CurrentHull != null && target.Entity is Hull) continue;
Door door = null;
if (target.Entity is Item item)
{
if (targetCharacter.AIController is EnemyAIController enemy)
{
targetingTag = "room";
}
door = item.GetComponent<Door>();
foreach (TargetingPriority prio in targetingPriorities.Values)
{
if (item.HasTag(prio.TargetTag))
{
targetingTag = "stronger";
}
}
}
else if (target.Entity is Structure s)
{
targetingTag = "wall";
if (aggressiveBoarding)
{
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 2 : 0;
if (valueModifier > 0)
{
targetingTag = "weaker";
}
}
}
}
else if (target.Entity != null)
{
//skip the target if it's a room and the character is already inside a sub
if (character.CurrentHull != null && target.Entity is Hull) continue;
Door door = null; Door door = null;
if (target.Entity is Item item) if (target.Entity is Item item)
{ {
@@ -1087,63 +1102,19 @@ namespace Barotrauma
} }
} }
} }
else if (target.Entity is Structure s)
{
targetingTag = "wall";
if (aggressiveBoarding)
{
// Ignore walls when inside.
valueModifier = character.CurrentHull == null ? 2 : 0;
if (valueModifier > 0)
{
// Ignore structures that doesn't have a body (not walls)
valueModifier *= s.HasBody ? 1 : 0;
}
for (int i = 0; i < s.Sections.Length; i++)
{
var section = s.Sections[i];
if (CanPassThroughHole(s, i))
{
// Ignore walls that can be passed through
valueModifier = 0;
break;
}
else if (section.gap != null)
{
// up to 100% priority increase for every gap in the wall
valueModifier *= 1 + section.gap.Open;
}
}
}
}
else else
{ {
targetingTag = "room"; targetingTag = "room";
} }
if (door != null) if (door != null)
{ {
// If there's not a more specific tag for the door
if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room")
{
targetingTag = "door";
}
bool isOutdoor = door.LinkedGap?.FlowTargetHull != null && !door.LinkedGap.IsRoomToRoom;
bool isOpen = door.IsOpen || door.Item.Condition <= 0.0f;
//increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside //increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
if (aggressiveBoarding) if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom)
{ {
if (character.CurrentHull == null) valueModifier = door.IsOpen ? 10 : 5;
{
valueModifier = isOutdoor ? 1 : 0;
valueModifier *= isOpen ? 5 : 1;
}
else
{
valueModifier = isOutdoor ? 0 : 1;
valueModifier *= isOpen ? 0 : 1;
}
} }
else if (isOpen) //ignore broken and open doors else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors
{ {
continue; continue;
} }
@@ -1162,7 +1133,7 @@ namespace Barotrauma
if (valueModifier == 0.0f) continue; if (valueModifier == 0.0f) continue;
Vector2 toTarget = target.WorldPosition - character.WorldPosition; Vector2 toTarget = target.WorldPosition - character.WorldPosition;
float dist = toTarget.Length(); dist = toTarget.Length();
//if the target has been within range earlier, the character will notice it more easily //if the target has been within range earlier, the character will notice it more easily
//(i.e. remember where the target was) //(i.e. remember where the target was)
@@ -1176,29 +1147,28 @@ namespace Barotrauma
// -> just ignore the distance and attack whatever has the highest priority // -> just ignore the distance and attack whatever has the highest priority
dist = Math.Max(dist, 100.0f); dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = GetTargetMemory(target); AITargetMemory targetMemory = FindTargetMemory(target);
if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y) if (Character.CurrentHull != null && Math.Abs(toTarget.Y) > Character.CurrentHull.Size.Y)
{ {
// Inside the sub, treat objects that are up or down, as they were farther away. // Inside the sub, treat objects that are up or down, as they were farther away.
dist *= 3; dist *= 3;
} }
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist); valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist);
if (valueModifier > targetValue) if (valueModifier > targetValue)
{ {
newTarget = target; SelectedAiTarget = target;
selectedTargetMemory = targetMemory; selectedTargetMemory = targetMemory;
priority = targetingPriorities[targetingTag]; targetingPriority = targetingPriorities[targetingTag];
targetValue = valueModifier; targetValue = valueModifier;
} }
} }
SelectedAiTarget = newTarget;
if (SelectedAiTarget != _previousAiTarget) if (SelectedAiTarget != _previousAiTarget)
{ {
wallTarget = null; wallTarget = null;
} }
return SelectedAiTarget; _previousAiTarget = SelectedAiTarget;
} }
private AITargetMemory GetTargetMemory(AITarget target) private AITargetMemory GetTargetMemory(AITarget target)
@@ -1208,21 +1178,24 @@ namespace Barotrauma
memory = new AITargetMemory(10); memory = new AITargetMemory(10);
targetMemories.Add(target, memory); targetMemories.Add(target, memory);
} }
memory = new AITargetMemory(10.0f);
targetMemories.Add(target, memory);
return memory; return memory;
} }
private List<AITarget> removals = new List<AITarget>(); private List<AITarget> removals = new List<AITarget>();
private void UpdateTargetMemories(float deltaTime) private void UpdateTargetMemories(float deltaTime)
{ {
removals.Clear(); List<AITarget> toBeRemoved = null;
foreach (var memory in targetMemories) foreach (KeyValuePair<AITarget, AITargetMemory> memory in targetMemories)
{ {
// Slowly decrease all memories memory.Value.Priority += 0.1f;
memory.Value.Priority -= memoryFadeTime * deltaTime; if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key))
// Remove targets that have no priority or have been removed
if (memory.Value.Priority <= 1 || !AITarget.List.Contains(memory.Key))
{ {
removals.Add(memory.Key); if (toBeRemoved == null) toBeRemoved = new List<AITarget>();
toBeRemoved.Add(memory.Key);
} }
} }
removals.ForEach(r => targetMemories.Remove(r)); removals.ForEach(r => targetMemories.Remove(r));
@@ -1238,9 +1211,25 @@ namespace Barotrauma
wallTarget = null; wallTarget = null;
} }
if (toBeRemoved != null)
{
foreach (AITarget target in toBeRemoved)
{
targetMemories.Remove(target);
}
}
#endregion
protected override void OnStateChanged(AIState from, AIState to)
{
latchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
}
private int GetMinimumPassableHoleCount() private int GetMinimumPassableHoleCount()
{ {
return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize); return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize);
} }
private bool CanPassThroughHole(Structure wall, int sectionIndex) private bool CanPassThroughHole(Structure wall, int sectionIndex)
@@ -245,7 +245,7 @@ namespace Barotrauma
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c.CurrentHull == Character.CurrentHull && !c.IsDead && if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC))) (c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
{ {
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders"); var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
newOrder = new Order(orderPrefab, Character.CurrentHull, null); newOrder = new Order(orderPrefab, Character.CurrentHull, null);
@@ -290,7 +290,7 @@ namespace Barotrauma
public override void OnAttacked(Character attacker, AttackResult attackResult) public override void OnAttacked(Character attacker, AttackResult attackResult)
{ {
float damage = attackResult.Damage; float damage = attackResult.Damage;
if (damage <= 0) { return; } if (damage < 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed) if (attacker == null || attacker.IsDead || attacker.Removed)
{ {
if (objectiveManager.CurrentOrder == null) if (objectiveManager.CurrentOrder == null)
@@ -466,9 +466,7 @@ namespace Barotrauma
// Even the smallest fire reduces the safety by 50% // Even the smallest fire reduces the safety by 50%
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X; float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
int enemyCount = Character.CharacterList.Count(e => int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || e.TeamID != character.TeamID));
e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious &&
(e.AIController is EnemyAIController || (e.TeamID != character.TeamID && character.TeamID != Character.TeamType.FriendlyNPC && e.TeamID != Character.TeamType.FriendlyNPC)));
// The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages) // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor; float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
@@ -193,7 +193,7 @@ namespace Barotrauma
// is not attached or is attached to something else // is not attached or is attached to something else
if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody) if (!IsAttached || IsAttached && attachJoints[0].BodyB == attachTargetBody)
{ {
if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.DamageRange * enemyAI.AttackingLimb.attack.DamageRange) if (Vector2.DistanceSquared(ConvertUnits.ToDisplayUnits(transformedAttachPos), enemyAI.AttackingLimb.WorldPosition) < enemyAI.AttackingLimb.attack.Range * enemyAI.AttackingLimb.attack.Range)
{ {
AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos); AttachToBody(character.AnimController.Collider, attachLimb, attachTargetBody, transformedAttachPos);
} }
@@ -95,7 +95,7 @@ namespace Barotrauma
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers) getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
{ {
GetItemPriority = GetItemPriority, GetItemPriority = GetItemPriority,
ignoredContainerIdentifiers = ignoredContainerIdentifiers IgnoreContainedItems = IgnoreAlreadyContainedItems
}; };
AddSubObjective(getItemObjective); AddSubObjective(getItemObjective);
return; return;
@@ -115,7 +115,6 @@ namespace Barotrauma
unreachable.Add(goToObjective.Target as Hull); unreachable.Add(goToObjective.Target as Hull);
} }
goToObjective = null; goToObjective = null;
SteeringManager.SteeringWander();
} }
} }
else if (currentHull != null) else if (currentHull != null)
@@ -132,11 +131,6 @@ namespace Barotrauma
foreach (Character enemy in Character.CharacterList) foreach (Character enemy in Character.CharacterList)
{ {
//don't run from friendly NPCs
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{ {
@@ -17,7 +17,7 @@ namespace Barotrauma
private string[] itemIdentifiers; private string[] itemIdentifiers;
private Item targetItem, moveToTarget; private Item targetItem, moveToTarget;
private int currSearchIndex; private int currSearchIndex;
public string[] ignoredContainerIdentifiers; public bool IgnoreContainedItems;
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private float currItemPriority; private float currItemPriority;
private bool equip; private bool equip;
@@ -99,12 +99,11 @@ namespace Barotrauma
FindTargetItem(); FindTargetItem();
if (targetItem == null || moveToTarget == null) if (targetItem == null || moveToTarget == null)
{ {
SteeringManager.SteeringWander(); SteeringManager.Reset();
return; return;
} }
if (moveToTarget.CurrentHull == character.CurrentHull && if (Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
{ {
int targetSlot = -1; int targetSlot = -1;
if (equip) if (equip)
@@ -197,12 +196,8 @@ namespace Barotrauma
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; } else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; } if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; } if (IgnoreContainedItems && item.Container != null) { continue; }
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
if (ignoredContainerIdentifiers != null && item.Container != null)
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
//if the item is inside a character's inventory, don't steal it unless the character is dead //if the item is inside a character's inventory, don't steal it unless the character is dead
if (item.ParentInventory is CharacterInventory) if (item.ParentInventory is CharacterInventory)
@@ -369,16 +369,7 @@ namespace Barotrauma
float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2; float movementAngle = MathUtils.VectorToAngle(movement) - MathHelper.PiOver2;
float mainLimbAngle = 0; float mainLimbAngle = (MainLimb.type == LimbType.Torso ? TorsoAngle.Value : HeadAngle.Value) * Dir;
if (MainLimb.type == LimbType.Torso && TorsoAngle.HasValue)
{
mainLimbAngle = TorsoAngle.Value;
}
else if (MainLimb.type == LimbType.Head && HeadAngle.HasValue)
{
mainLimbAngle = HeadAngle.Value;
}
mainLimbAngle *= Dir;
while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi) while (MainLimb.Rotation - (movementAngle + mainLimbAngle) > MathHelper.Pi)
{ {
movementAngle += MathHelper.TwoPi; movementAngle += MathHelper.TwoPi;
@@ -417,7 +408,7 @@ namespace Barotrauma
} }
} }
} }
else else if (MainLimb.type == LimbType.Head && HeadAngle.HasValue)
{ {
movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2; movementAngle = Dir > 0 ? -MathHelper.PiOver2 : MathHelper.PiOver2;
if (MainLimb.type == LimbType.Head && HeadAngle.HasValue) if (MainLimb.type == LimbType.Head && HeadAngle.HasValue)
@@ -692,6 +683,12 @@ namespace Barotrauma
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength); limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
} }
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
} }
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque) private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
@@ -99,7 +99,7 @@ namespace Barotrauma
public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/"; public static string GetDefaultFolder(string speciesName) => $"Content/Characters/{speciesName.CapitaliseFirstInvariant()}/Animations/";
public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml"; public static string GetDefaultFile(string speciesName, AnimationType animType) => $"{GetFolder(speciesName)}{GetDefaultFileName(speciesName, animType)}.xml";
public static string GetFolder(string speciesName) protected static string GetFolder(string speciesName)
{ {
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty); var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("animations")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
@@ -79,7 +79,7 @@ namespace Barotrauma
new XAttribute("sourcerect", $"0, 0, 1, 1"))) new XAttribute("sourcerect", $"0, 0, 1, 1")))
}; };
public static string GetFolder(string speciesName) protected static string GetFolder(string speciesName)
{ {
var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty); var folder = XMLExtensions.TryLoadXml(Character.GetConfigFile(speciesName))?.Root?.Element("ragdolls")?.GetAttributeString("folder", string.Empty);
if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default") if (string.IsNullOrEmpty(folder) || folder.ToLowerInvariant() == "default")
@@ -1035,6 +1035,8 @@ namespace Barotrauma
CheckValidity(); CheckValidity();
CheckValidity();
UpdateNetPlayerPosition(deltaTime); UpdateNetPlayerPosition(deltaTime);
CheckDistFromCollider(); CheckDistFromCollider();
UpdateCollisionCategories(); UpdateCollisionCategories();
@@ -1295,42 +1297,17 @@ namespace Barotrauma
UpdateProjSpecific(deltaTime); UpdateProjSpecific(deltaTime);
} }
public bool Invalid { get; private set; } private void CheckValidity()
private int validityResets;
private bool CheckValidity()
{ {
bool isColliderValid = CheckValidity(Collider); CheckValidity(Collider);
bool limbsValid = true;
foreach (Limb limb in limbs) foreach (Limb limb in limbs)
{ {
if (limb.body == null || !limb.body.Enabled) { continue; } if (limb.body == null || !limb.body.Enabled) { continue; }
if (!CheckValidity(limb.body)) CheckValidity(limb.body);
{
limbsValid = false;
break;
}
} }
bool isValid = isColliderValid && limbsValid;
if (!isValid)
{
validityResets++;
if (validityResets > 1)
{
Invalid = true;
DebugConsole.ThrowError("Invalid ragdoll physics. Ragdoll freezed to prevent crashes.");
Collider.SetTransform(Vector2.Zero, 0.0f);
foreach (Limb limb in Limbs)
{
limb.body.SetTransform(Collider.SimPosition, 0.0f);
limb.body.ResetDynamics();
}
Frozen = true;
}
}
return isValid;
} }
private bool CheckValidity(PhysicsBody body) private void CheckValidity(PhysicsBody body)
{ {
string errorMsg = null; string errorMsg = null;
string bodyName = body.UserData is Limb ? "Limb" : "Collider"; string bodyName = body.UserData is Limb ? "Limb" : "Collider";
@@ -1352,19 +1329,6 @@ namespace Barotrauma
} }
if (errorMsg != null) if (errorMsg != null)
{ {
if (character.IsRemotePlayer)
{
errorMsg += " Ragdoll controlled remotely.";
}
if (SimplePhysicsEnabled)
{
errorMsg += " Simple physics enabled.";
}
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
#if DEBUG #if DEBUG
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
#else #else
@@ -1382,7 +1346,7 @@ namespace Barotrauma
limb.body.ResetDynamics(); limb.body.ResetDynamics();
} }
SetInitialLimbPositions(); SetInitialLimbPositions();
return false; return;
} }
return true; return true;
} }
@@ -808,7 +808,6 @@ namespace Barotrauma
public void LoadHeadAttachments() public void LoadHeadAttachments()
{ {
if (Info == null) { return; }
if (AnimController == null) { return; } if (AnimController == null) { return; }
var head = AnimController.GetLimb(LimbType.Head); var head = AnimController.GetLimb(LimbType.Head);
if (head == null) { return; } if (head == null) { return; }
@@ -1111,15 +1110,13 @@ namespace Barotrauma
ViewTarget = null; ViewTarget = null;
if (!AllowInput) return; if (!AllowInput) return;
if (Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)) Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
if (Controlled == this)
{ {
SmoothedCursorPosition = cursorPosition; SmoothedCursorPosition = cursorPosition;
} }
else else
{ {
//apply some smoothing to the cursor positions of remote players when playing as a client
//to make aiming look a little less choppy
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff); smoothedCursorDiff = NetConfig.InterpolateCursorPositionError(smoothedCursorDiff);
SmoothedCursorPosition = cursorPosition - smoothedCursorDiff; SmoothedCursorPosition = cursorPosition - smoothedCursorDiff;
} }
@@ -1667,12 +1664,10 @@ namespace Barotrauma
focusedItem = null; focusedItem = null;
} }
findFocusedTimer -= deltaTime; findFocusedTimer -= deltaTime;
} }
#endif #endif
//climb ladders automatically when pressing up/down inside their trigger area //climb ladders automatically when pressing up/down inside their trigger area
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>(); if (SelectedConstruction == null && !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
if ((SelectedConstruction == null || currentLadder != null) &&
!AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
{ {
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down); bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
bool isControlled = Controlled == this; bool isControlled = Controlled == this;
@@ -1683,19 +1678,6 @@ namespace Barotrauma
float minDist = float.PositiveInfinity; float minDist = float.PositiveInfinity;
foreach (Ladder ladder in Ladder.List) foreach (Ladder ladder in Ladder.List)
{ {
if (ladder == currentLadder)
{
continue;
}
else if (currentLadder != null)
{
//only switch from ladder to another if the ladders are above the current ladders and pressing up, or vice versa
if (ladder.Item.WorldPosition.Y > currentLadder.Item.WorldPosition.Y != IsKeyDown(InputType.Up))
{
continue;
}
}
if (CanInteractWith(ladder.Item, out float dist, checkLinked: false) && dist < minDist) if (CanInteractWith(ladder.Item, out float dist, checkLinked: false) && dist < minDist)
{ {
minDist = dist; minDist = dist;
@@ -1894,6 +1876,8 @@ namespace Barotrauma
} }
speechImpedimentSet = false; speechImpedimentSet = false;
if (needsAir) if (needsAir)
{ {
bool protectedFromPressure = PressureProtection > 0.0f; bool protectedFromPressure = PressureProtection > 0.0f;
@@ -1950,23 +1934,9 @@ namespace Barotrauma
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true; bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true;
if (IsForceRagdolled) if (IsForceRagdolled)
{
IsRagdolled = IsForceRagdolled; IsRagdolled = IsForceRagdolled;
} else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll
//Keep us ragdolled if we were forced or we're too speedy to unragdoll IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f))
{
if (ragdollingLockTimer > 0.0f)
{
ragdollingLockTimer -= deltaTime;
}
else
{
bool wasRagdolled = IsRagdolled;
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
if (wasRagdolled != IsRagdolled) { ragdollingLockTimer = 0.25f; }
}
}
UpdateSightRange(); UpdateSightRange();
UpdateSoundRange(); UpdateSoundRange();
@@ -2548,14 +2518,7 @@ namespace Barotrauma
{ {
item.Submarine = inventory.Owner.Submarine; item.Submarine = inventory.Owner.Submarine;
var itemElement = item.Save(parentElement); var itemElement = item.Save(parentElement);
itemElement.Add(new XAttribute("i", Array.IndexOf(inventory.Items, item)));
List<int> slotIndices = new List<int>();
for (int i = 0; i < inventory.Capacity; i++)
{
if (inventory.Items[i] == item) { slotIndices.Add(i); }
}
itemElement.Add(new XAttribute("i", string.Join(",", slotIndices)));
foreach (ItemContainer container in item.GetComponents<ItemContainer>()) foreach (ItemContainer container in item.GetComponents<ItemContainer>())
{ {
@@ -801,37 +801,13 @@ namespace Barotrauma
{ {
foreach (XElement itemElement in element.Elements()) foreach (XElement itemElement in element.Elements())
{ {
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true); var newItem = Item.Load(itemElement, inventory.Owner.Submarine);
if (newItem == null) { continue; } int slotIndex = itemElement.GetAttributeInt("i", 0);
if (newItem == null) continue;
if (!MathUtils.NearlyEqual(newItem.Condition, newItem.MaxCondition)) SpawnInventoryItemProjSpecific(newItem);
{
GameMain.NetworkMember.CreateEntityEvent(newItem, new object[] { NetEntityEvent.Type.Status });
}
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 }); inventory.TryPutItem(newItem, slotIndex, false, false, null);
if (!slotIndices.Any())
{
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
continue;
}
inventory.TryPutItem(newItem, slotIndices[0], false, false, null);
//force the item to the correct slots
// e.g. putting the item in a hand slot will also put it in the first available Any-slot,
// which may not be where it actually was
for (int i = 0; i < inventory.Capacity; i++)
{
if (slotIndices.Contains(i))
{
inventory.Items[i] = newItem;
}
else if (inventory.Items[i] == newItem)
{
inventory.Items[i] = null;
}
}
int itemContainerIndex = 0; int itemContainerIndex = 0;
var itemContainers = newItem.GetComponents<ItemContainer>().ToList(); var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
@@ -845,6 +821,8 @@ namespace Barotrauma
} }
} }
partial void SpawnInventoryItemProjSpecific(Item item);
public void ReloadHeadAttachments() public void ReloadHeadAttachments()
{ {
ResetLoadedAttachments(); ResetLoadedAttachments();
@@ -13,17 +13,17 @@ namespace Barotrauma
public readonly AnimController.Animation Animation; public readonly AnimController.Animation Animation;
public CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None) public CharacterStateInfo(Vector2 pos, float rotation, Vector2 velocity, float angularVelocity, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, velocity, angularVelocity, 0, time, dir, interact, animation) : this(pos, rotation, velocity, angularVelocity, 0, time, dir, interact, animation)
{ {
} }
public CharacterStateInfo(Vector2 pos, float? rotation, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None) public CharacterStateInfo(Vector2 pos, float rotation, UInt16 ID, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, interact, animation) : this(pos, rotation, Vector2.Zero, 0.0f, ID, 0.0f, dir, interact, animation)
{ {
} }
protected CharacterStateInfo(Vector2 pos, float? rotation, Vector2 velocity, float? angularVelocity, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None) protected CharacterStateInfo(Vector2 pos, float rotation, Vector2 velocity, float angularVelocity, UInt16 ID, float time, Direction dir, Entity interact, AnimController.Animation animation = AnimController.Animation.None)
: base(pos, rotation, velocity, angularVelocity, ID, time) : base(pos, rotation, velocity, angularVelocity, ID, time)
{ {
Direction = dir; Direction = dir;
@@ -151,12 +151,9 @@ namespace Barotrauma
//how high the strength has to be for the affliction to take affect //how high the strength has to be for the affliction to take affect
public readonly float ActivationThreshold = 0.0f; public readonly float ActivationThreshold = 0.0f;
//how high the strength has to be for the affliction icon to be shown in the UI //how high the strength has to be for the affliction icon to be shown in the UI
public readonly float ShowIconThreshold = 0.05f; public readonly float ShowIconThreshold = 0.0f;
public readonly float MaxStrength = 100.0f; public readonly float MaxStrength = 100.0f;
//how high the strength has to be for the affliction icon to be shown with a health scanner
public readonly float ShowInHealthScannerThreshold = 0.05f;
public float BurnOverlayAlpha; public float BurnOverlayAlpha;
public float DamageOverlayAlpha; public float DamageOverlayAlpha;
@@ -257,11 +254,9 @@ namespace Barotrauma
} }
ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f); ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f)); ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", ActivationThreshold);
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f); MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f); DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f); BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);
@@ -460,7 +460,6 @@ namespace Barotrauma
{ {
affliction.Strength = 0.0f; affliction.Strength = 0.0f;
} }
CalculateVitality();
} }
private void AddLimbAffliction(Limb limb, Affliction newAffliction) private void AddLimbAffliction(Limb limb, Affliction newAffliction)
@@ -491,10 +491,10 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated. /// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
/// </summary> /// </summary>
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1) public bool UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
{ {
attackResult = default(AttackResult); attackResult = default(AttackResult);
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos)); float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition));
bool wasRunning = attack.IsRunning; bool wasRunning = attack.IsRunning;
attack.UpdateAttackTimer(deltaTime); attack.UpdateAttackTimer(deltaTime);
@@ -511,7 +511,7 @@ namespace Barotrauma
ignoredBodies.Add(character.AnimController.Collider.FarseerBody); ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
structureBody = Submarine.PickBody( structureBody = Submarine.PickBody(
SimPosition, attackSimPos, SimPosition, attackPosition,
ignoredBodies, Physics.CollisionWall); ignoredBodies, Physics.CollisionWall);
if (damageTarget is Item) if (damageTarget is Item)
@@ -520,15 +520,14 @@ namespace Barotrauma
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example. // Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
wasHit = true; wasHit = true;
} }
else if (damageTarget is Structure wall && structureBody != null && else if (damageTarget is Structure && structureBody?.UserData is Structure)
(structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine)))
{ {
// If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful // If the attack is aimed to a structure and hits a structure, it's successful
wasHit = true; wasHit = true;
} }
else else
{ {
// If there is nothing between, the hit is successful // If the attack is aimed to a character but hits a structure, the hit is blocked.
wasHit = structureBody == null; wasHit = structureBody == null;
} }
} }
@@ -608,7 +607,7 @@ namespace Barotrauma
attack.SetCoolDown(); attack.SetCoolDown();
} }
Vector2 diff = attackSimPos - SimPosition; Vector2 diff = attackPosition - SimPosition;
bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f; bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
if (applyForces) if (applyForces)
{ {
@@ -621,13 +620,13 @@ namespace Barotrauma
Limb limb = character.AnimController.Limbs[limbIndex]; Limb limb = character.AnimController.Limbs[limbIndex];
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA; Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos); limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
} }
} }
else else
{ {
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA; Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos); body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
} }
} }
return wasHit; return wasHit;
@@ -912,7 +912,7 @@ namespace Barotrauma
ThrowError(args[0] + " is not a valid latency value."); ThrowError(args[0] + " is not a valid latency value.");
return; return;
} }
if (!float.TryParse(args[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float randomLatency)) if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float randomLatency))
{ {
ThrowError(args[1] + " is not a valid latency value."); ThrowError(args[1] + " is not a valid latency value.");
return; return;
@@ -103,6 +103,12 @@ namespace Barotrauma
if (missionType == MissionType.Random) if (missionType == MissionType.Random)
{ {
allowedMissions.AddRange(MissionPrefab.List); allowedMissions.AddRange(MissionPrefab.List);
#if SERVER
if (GameMain.Server != null)
{
allowedMissions.RemoveAll(mission => !GameMain.Server.ServerSettings.AllowedRandomMissionTypes.Contains(mission.type));
}
#endif
} }
else if (missionType == MissionType.None) else if (missionType == MissionType.None)
{ {
@@ -118,11 +124,6 @@ namespace Barotrauma
{ {
allowedMissions.RemoveAll(m => !m.IsAllowed(locations[0], locations[1])); allowedMissions.RemoveAll(m => !m.IsAllowed(locations[0], locations[1]));
} }
if (allowedMissions.Count == 0)
{
return null;
}
int probabilitySum = allowedMissions.Sum(m => m.Commonness); int probabilitySum = allowedMissions.Sum(m => m.Commonness);
int randomNumber = rand.NextInt32() % probabilitySum; int randomNumber = rand.NextInt32() % probabilitySum;
@@ -208,7 +208,7 @@ namespace Barotrauma
DockingPort myPort = null, outPostPort = null; DockingPort myPort = null, outPostPort = null;
foreach (DockingPort port in DockingPort.List) foreach (DockingPort port in DockingPort.List)
{ {
if (port.IsHorizontal || port.Docked) { continue; } if (port.IsHorizontal) { continue; }
if (port.Item.Submarine == level.StartOutpost) if (port.Item.Submarine == level.StartOutpost)
{ {
outPostPort = port; outPostPort = port;
@@ -168,7 +168,7 @@ namespace Barotrauma
} }
} }
private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f, microphoneVolume = 1.0f; private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f;
public float SoundVolume public float SoundVolume
{ {
@@ -211,14 +211,6 @@ namespace Barotrauma
} }
} }
public float MicrophoneVolume
{
get { return microphoneVolume; }
set
{
microphoneVolume = MathHelper.Clamp(value, 0.1f, 5.0f);
}
}
public string Language public string Language
{ {
get { return TextManager.Language; } get { return TextManager.Language; }
@@ -51,11 +51,6 @@ namespace Barotrauma.Items.Components
public PhysicsBody Body { get; private set; } public PhysicsBody Body { get; private set; }
private float RepairThreshold
{
get { return item.GetComponent<Repairable>()?.ShowRepairUIThreshold ?? 0.0f; }
}
private float stuck; private float stuck;
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float Stuck public float Stuck
@@ -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(picker, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer); 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);
@@ -208,9 +208,8 @@ namespace Barotrauma.Items.Components
public ItemComponent(Item item, XElement element) public ItemComponent(Item item, XElement element)
{ {
this.item = item; this.item = item;
originalElement = element;
name = element.Name.ToString(); name = element.Name.ToString();
SerializableProperties = SerializableProperty.GetProperties(this); properties = SerializableProperty.GetProperties(this);
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(); requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>();
requiredSkills = new List<Skill>(); requiredSkills = new List<Skill>();
@@ -244,9 +243,18 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Invalid pick key in " + element + "!", e); DebugConsole.ThrowError("Invalid pick key in " + element + "!", e);
} }
SerializableProperties = SerializableProperty.DeserializeProperties(this, element); properties = SerializableProperty.DeserializeProperties(this, element);
ParseMsg(); #if CLIENT
string msg = TextManager.Get(Msg, true);
if (msg != null)
{
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
}
Msg = msg;
}
#endif
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
@@ -534,6 +542,7 @@ namespace Barotrauma.Items.Components
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return 0.0f; return 0.0f;
} }
float average = skillSuccessSum / requiredSkills.Count;
float skillSuccessSum = 0.0f; float skillSuccessSum = 0.0f;
for (int i = 0; i < requiredSkills.Count; i++) for (int i = 0; i < requiredSkills.Count; i++)
@@ -622,11 +631,53 @@ namespace Barotrauma.Items.Components
public virtual void Load(XElement componentElement) public virtual void Load(XElement componentElement)
{ {
if (componentElement == null) return; if (componentElement == null) return;
foreach (XAttribute attribute in componentElement.Attributes()) foreach (XAttribute attribute in componentElement.Attributes())
{ {
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue; if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(this, attribute.Value); property.TrySetValue(this, attribute.Value);
} }
#if CLIENT
string msg = TextManager.Get(Msg, true);
if (msg != null)
{
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
msg = msg.Replace("[" + inputType.ToString().ToLowerInvariant() + "]", GameMain.Config.KeyBind(inputType).ToString());
}
Msg = msg;
}
#endif
var prevRequiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>(requiredItems);
bool overrideRequiredItems = false;
foreach (XElement subElement in componentElement.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "requireditem":
if (!overrideRequiredItems) requiredItems.Clear();
overrideRequiredItems = true;
RelatedItem newRequiredItem = RelatedItem.Load(subElement, item.Name);
if (newRequiredItem == null) continue;
var prevRequiredItem = prevRequiredItems.ContainsKey(newRequiredItem.Type) ?
prevRequiredItems[newRequiredItem.Type].Find(ri => ri.JoinedIdentifiers == newRequiredItem.JoinedIdentifiers) : null;
if (prevRequiredItem != null)
{
newRequiredItem.statusEffects = prevRequiredItem.statusEffects;
newRequiredItem.Msg = prevRequiredItem.Msg;
}
if (!requiredItems.ContainsKey(newRequiredItem.Type))
{
requiredItems[newRequiredItem.Type] = new List<RelatedItem>();
}
requiredItems[newRequiredItem.Type].Add(newRequiredItem);
break;
}
}
ParseMsg(); ParseMsg();
OverrideRequiredItems(componentElement); OverrideRequiredItems(componentElement);
} }
@@ -201,19 +201,19 @@ namespace Barotrauma.Items.Components
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess); tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance); allowedTurbineOutput = new Vector2(correctTurbineOutput - tolerance, correctTurbineOutput + tolerance);
float temperatureTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess); optimalTemperature = Vector2.Lerp(new Vector2(40.0f, 60.0f), new Vector2(30.0f, 70.0f), degreeOfSuccess);
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess); allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
optimalFissionRate = Vector2.Lerp(new Vector2(30, AvailableFuel - 20), new Vector2(20, AvailableFuel - 10), degreeOfSuccess); float fissionRateTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalFissionRate.X = Math.Min(optimalFissionRate.X, optimalFissionRate.Y - 10); optimalFissionRate = Vector2.Lerp(new Vector2(40.0f, 70.0f), new Vector2(30.0f, 85.0f), degreeOfSuccess);
allowedFissionRate = Vector2.Lerp(new Vector2(20, AvailableFuel), new Vector2(10, AvailableFuel), degreeOfSuccess); allowedFissionRate = Vector2.Lerp(new Vector2(30.0f, 85.0f), new Vector2(20.0f, 98.0f), degreeOfSuccess);
allowedFissionRate.X = Math.Min(allowedFissionRate.X, allowedFissionRate.Y - 10);
float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f; float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f;
float temperatureDiff = (heatAmount - turbineOutput) - Temperature; float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff)); Temperature += MathHelper.Clamp(Math.Sign(temperatureDiff) * 10.0f * deltaTime, -Math.Abs(temperatureDiff), Math.Abs(temperatureDiff));
if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime; if (item.InWater && AvailableFuel < 100.0f) Temperature -= 12.0f * deltaTime;
FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime); FissionRate = MathHelper.Lerp(fissionRate, Math.Min(targetFissionRate, AvailableFuel), deltaTime);
TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime); TurbineOutput = MathHelper.Lerp(turbineOutput, targetTurbineOutput, deltaTime);
@@ -364,7 +364,7 @@ namespace Barotrauma.Items.Components
} }
else if (-currPowerConsumption < load) else if (-currPowerConsumption < load)
{ {
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, 100.0f); targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, allowedFissionRate.Y);
} }
targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f); targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f);
} }
@@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components
} }
continue; continue;
} }
if (!pt.IsActive || !pt.CanTransfer) { continue; } if (!pt.IsActive) { continue; }
gridLoad += pt.PowerLoad; gridLoad += pt.PowerLoad;
gridPower -= pt.CurrPowerConsumption; gridPower -= pt.CurrPowerConsumption;
@@ -209,9 +209,9 @@ namespace Barotrauma.Items.Components
Charge -= CurrPowerOutput / 3600.0f; Charge -= CurrPowerOutput / 3600.0f;
} }
item.SendSignal(0, ((int)Charge).ToString(), "charge", null); item.SendSignal(0, Charge.ToString(), "charge", null);
item.SendSignal(0, ((int)((Charge / capacity) * 100)).ToString(), "charge_%", null); item.SendSignal(0, ((Charge / capacity) * 100).ToString(), "charge_%", null);
item.SendSignal(0, ((int)((RechargeSpeed / maxRechargeSpeed) * 100)).ToString(), "charge_rate", null); item.SendSignal(0, ((RechargeSpeed / maxRechargeSpeed) * 100).ToString(), "charge_rate", null);
foreach (Pair<Powered, Connection> connected in directlyConnected) foreach (Pair<Powered, Connection> connected in directlyConnected)
{ {
@@ -319,13 +319,7 @@ namespace Barotrauma.Items.Components
ApplyStatusEffects(ActionType.OnActive, deltaTime, null); ApplyStatusEffects(ActionType.OnActive, deltaTime, null);
//float maxPower = this is RelayComponent relayComponent ? relayComponent.MaxPower : float.PositiveInfinity; float maxPower = this is RelayComponent relayComponent ? relayComponent.MaxPower : float.PositiveInfinity;
RelayComponent thisRelayComponent = this as RelayComponent;
if (thisRelayComponent != null)
{
clampPower = Math.Min(Math.Min(clampPower, thisRelayComponent.MaxPower), powerLoad);
clampLoad = Math.Min(clampLoad, thisRelayComponent.MaxPower);
}
foreach (Connection c in PowerConnections) foreach (Connection c in PowerConnections)
{ {
@@ -363,8 +357,6 @@ namespace Barotrauma.Items.Components
continue; continue;
} }
float addLoad = 0.0f;
float addPower = 0.0f;
if (powered is PowerContainer powerContainer) if (powered is PowerContainer powerContainer)
{ {
if (recipient.Name == "power_in") if (recipient.Name == "power_in")
@@ -373,7 +365,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
addPower = powerContainer.CurrPowerOutput; fullPower += Math.Min(powerContainer.CurrPowerOutput, maxPower);
} }
} }
else else
@@ -388,16 +380,10 @@ namespace Barotrauma.Items.Components
//negative power consumption = the construction is a //negative power consumption = the construction is a
//generator/battery or another junction box //generator/battery or another junction box
{ {
addPower -= powered.CurrPowerConsumption; fullPower -= Math.Max(powered.CurrPowerConsumption, -maxPower);
} }
} }
}
if (addPower + fullPower > clampPower) { addPower -= (addPower + fullPower) - clampPower; };
if (addPower > 0) { fullPower += addPower; }
if (addLoad + fullLoad > clampLoad) { addLoad -= (addLoad + fullLoad) - clampLoad; };
if (addLoad > 0) { fullLoad += addLoad; }
}
} }
} }
} }
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
public static float SkillIncreaseMultiplier = 0.4f; public static float SkillIncreaseMultiplier = 0.4f;
private string header; private string header;
private float fixDurationLowSkill, fixDurationHighSkill;
private float deteriorationTimer; private float deteriorationTimer;
@@ -50,20 +52,17 @@ namespace Barotrauma.Items.Components
set; set;
} }
[Serialize(100.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The amount of time it takes to fix the item with insufficient skill levels.")] /*private float repairProgress;
public float FixDurationLowSkill public float RepairProgress
{ {
get; get { return repairProgress; }
set; set
} {
repairProgress = MathHelper.Clamp(value, 0.0f, 1.0f);
[Serialize(10.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, ToolTip = "The amount of time it takes to fix the item with sufficient skill levels.")] if (repairProgress >= 1.0f && currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None;
public float FixDurationHighSkill }
{ }*/
get;
set;
}
private Character currentFixer; private Character currentFixer;
public Character CurrentFixer public Character CurrentFixer
{ {
@@ -84,6 +83,8 @@ namespace Barotrauma.Items.Components
this.item = item; this.item = item;
header = element.GetAttributeString("name", ""); header = element.GetAttributeString("name", "");
fixDurationLowSkill = element.GetAttributeFloat("fixdurationlowskill", 100.0f);
fixDurationHighSkill = element.GetAttributeFloat("fixdurationhighskill", 5.0f);
InitProjSpecific(element); InitProjSpecific(element);
} }
@@ -159,7 +160,7 @@ namespace Barotrauma.Items.Components
} }
bool wasBroken = !item.IsFullCondition; bool wasBroken = !item.IsFullCondition;
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor); float fixDuration = MathHelper.Lerp(fixDurationLowSkill, fixDurationHighSkill, successFactor);
if (fixDuration <= 0.0f) if (fixDuration <= 0.0f)
{ {
item.Condition = item.MaxCondition; item.Condition = item.MaxCondition;
@@ -185,5 +186,26 @@ namespace Barotrauma.Items.Components
{ {
character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f)); character.AnimController.UpdateUseItem(false, item.WorldPosition + new Vector2(0.0f, 100.0f) * ((item.Condition / item.MaxCondition) % 0.1f));
} }
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
msg.Write(deteriorationTimer);
}
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{
deteriorationTimer = msg.ReadSingle();
}
public void ClientWrite(NetBuffer msg, object[] extraData = null)
{
//no need to write anything, just letting the server know we started repairing
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
if (c.Character == null) return;
StartRepairing(c.Character);
}
} }
} }
@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
IsActive = value; IsActive = value;
#if SERVER #if SERVER
if (GameMain.Server != null && GameMain.Server.GameStarted) { item.CreateServerEvent(this); } if (GameMain.Server != null) item.CreateServerEvent(this);
#endif #endif
} }
} }
@@ -177,21 +177,14 @@ namespace Barotrauma.Items.Components
Vector2 nodePos = refSub == null ? Vector2 nodePos = refSub == null ?
newConnection.Item.Position : newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition; newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) break; if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break; if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
//make sure we place the node at the correct end of the wire (the end that's closest to the new node pos) //make sure we place the node at the correct end of the wire (the end that's closest to the new node pos)
int newNodeIndex = 0; int newNodeIndex = 0;
if (nodes.Count > 1) if (nodes.Count > 1)
{
if (Vector2.DistanceSquared(nodes[nodes.Count-1], nodePos) < Vector2.DistanceSquared(nodes[0], nodePos))
{
newNodeIndex = nodes.Count;
}
}
if (newNodeIndex == 0)
{ {
nodes.Insert(0, nodePos); nodes.Insert(0, nodePos);
} }
@@ -433,10 +433,9 @@ namespace Barotrauma.Items.Components
if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill")) if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill"))
{ {
ItemContainer container = null; ItemContainer container = null;
Item containerItem = null;
foreach (MapEntity e in item.linkedTo) foreach (MapEntity e in item.linkedTo)
{ {
containerItem = e as Item; var containerItem = e as Item;
if (containerItem == null) continue; if (containerItem == null) continue;
container = containerItem.GetComponent<ItemContainer>(); container = containerItem.GetComponent<ItemContainer>();
@@ -454,7 +453,7 @@ namespace Barotrauma.Items.Components
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container); var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container);
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f); character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
containShellObjective.MinContainedAmount = usableProjectileCount + 1; containShellObjective.MinContainedAmount = usableProjectileCount + 1;
containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier }; containShellObjective.IgnoreAlreadyContainedItems = true;
objective.AddSubObjective(containShellObjective); objective.AddSubObjective(containShellObjective);
return false; return false;
} }
@@ -59,7 +59,8 @@ namespace Barotrauma
public PhysicsBody body; public PhysicsBody body;
public readonly XElement StaticBodyConfig; public readonly XElement StaticBodyConfig;
private bool needsPositionUpdate;
private float lastSentCondition; private float lastSentCondition;
private float sendConditionUpdateTimer; private float sendConditionUpdateTimer;
private bool conditionUpdatePending; private bool conditionUpdatePending;
@@ -88,7 +89,7 @@ namespace Barotrauma
if (hasInGameEditableProperties == null) if (hasInGameEditableProperties == null)
{ {
hasInGameEditableProperties = false; hasInGameEditableProperties = false;
if (SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any())) if (properties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
{ {
hasInGameEditableProperties = true; hasInGameEditableProperties = true;
} }
@@ -97,7 +98,7 @@ namespace Barotrauma
foreach (ItemComponent component in components) foreach (ItemComponent component in components)
{ {
if (!component.AllowInGameEditing) { continue; } if (!component.AllowInGameEditing) { continue; }
if (component.SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any())) if (component.properties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
{ {
hasInGameEditableProperties = true; hasInGameEditableProperties = true;
break; break;
@@ -211,14 +212,14 @@ namespace Barotrauma
set { spriteColor = value; } set { spriteColor = value; }
} }
[Serialize("1.0,1.0,1.0,1.0", true), Editable] [Serialize("1.0,1.0,1.0,1.0", false), Editable]
public Color InventoryIconColor public Color InventoryIconColor
{ {
get; get;
protected set; protected set;
} }
[Serialize("1.0,1.0,1.0,1.0", true), Editable(ToolTip = "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")] [Serialize("1.0,1.0,1.0,1.0", false), Editable(ToolTip = "Changes the color of the item this item is contained inside. Only has an effect if either of the UseContainedSpriteColor or UseContainedInventoryIconColor property of the container is set to true.")]
public Color ContainerColor public Color ContainerColor
{ {
get; get;
@@ -274,11 +275,12 @@ namespace Barotrauma
SetActiveSprite(); SetActiveSprite();
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !MathUtils.NearlyEqual(lastSentCondition, condition)) if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && lastSentCondition != condition)
{ {
if (Math.Abs(lastSentCondition - condition) > 1.0f || condition == 0.0f || condition == Prefab.Health) if (Math.Abs(lastSentCondition - condition) > 1.0f || condition == 0.0f || condition == Prefab.Health)
{ {
conditionUpdatePending = true; GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
lastSentCondition = condition;
} }
} }
} }
@@ -583,10 +585,10 @@ namespace Barotrauma
public override MapEntity Clone() public override MapEntity Clone()
{ {
Item clone = new Item(rect, Prefab, Submarine, callOnItemLoaded: false); Item clone = new Item(rect, Prefab, Submarine, callOnItemLoaded: false);
foreach (KeyValuePair<string, SerializableProperty> property in SerializableProperties) foreach (KeyValuePair<string, SerializableProperty> property in properties)
{ {
if (!property.Value.Attributes.OfType<Editable>().Any()) continue; if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this)); clone.properties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
} }
if (components.Count != clone.components.Count) if (components.Count != clone.components.Count)
@@ -603,7 +605,7 @@ namespace Barotrauma
foreach (KeyValuePair<string, SerializableProperty> property in components[i].SerializableProperties) foreach (KeyValuePair<string, SerializableProperty> property in components[i].SerializableProperties)
{ {
if (!property.Value.Attributes.OfType<Editable>().Any()) continue; if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
clone.components[i].SerializableProperties[property.Key].TrySetValue(clone.components[i], property.Value.GetValue(components[i])); clone.components[i].properties[property.Key].TrySetValue(clone.components[i], property.Value.GetValue(components[i]));
} }
//clone requireditem identifiers //clone requireditem identifiers
@@ -995,21 +997,6 @@ namespace Barotrauma
aiTarget.SightRange -= deltaTime * 1000.0f; aiTarget.SightRange -= deltaTime * 1000.0f;
aiTarget.SoundRange -= deltaTime * 1000.0f; aiTarget.SoundRange -= deltaTime * 1000.0f;
} }
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer)
{
sendConditionUpdateTimer -= deltaTime;
if (conditionUpdatePending)
{
if (sendConditionUpdateTimer <= 0.0f)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
lastSentCondition = condition;
sendConditionUpdateTimer = NetConfig.ItemConditionUpdateInterval;
conditionUpdatePending = false;
}
}
}
ApplyStatusEffects(ActionType.Always, deltaTime, null); ApplyStatusEffects(ActionType.Always, deltaTime, null);
@@ -1574,15 +1561,12 @@ namespace Barotrauma
return isCombined; return isCombined;
} }
public void Drop(Character dropper, bool createNetworkEvent = true) public void Drop(Character dropper)
{ {
if (createNetworkEvent) if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
{ {
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed && parentInventory.CreateNetworkEvent();
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
{
parentInventory.CreateNetworkEvent();
}
} }
foreach (ItemComponent ic in components) { ic.Drop(dropper); } foreach (ItemComponent ic in components) { ic.Drop(dropper); }
@@ -1825,20 +1809,8 @@ namespace Barotrauma
} }
partial void UpdateNetPosition(float deltaTime); partial void UpdateNetPosition(float deltaTime);
public static Item Load(XElement element, Submarine submarine) public static Item Load(XElement element, Submarine submarine)
{
return Load(element, submarine, createNetworkEvent: false);
}
/// <summary>
/// Instantiate a new item and load its data from the XML element.
/// </summary>
/// <param name="element">The element containing the data of the item</param>
/// <param name="submarine">The submarine to spawn the item in (can be null)</param>
/// <param name="createNetworkEvent">Should an EntitySpawner event be created to notify clients about the item being created.</param>
/// <returns></returns>
public static Item Load(XElement element, Submarine submarine, bool createNetworkEvent)
{ {
string name = element.Attribute("name").Value; string name = element.Attribute("name").Value;
string identifier = element.GetAttributeString("identifier", ""); string identifier = element.GetAttributeString("identifier", "");
@@ -1884,16 +1856,9 @@ namespace Barotrauma
linkedToID = new List<ushort>() linkedToID = new List<ushort>()
}; };
#if SERVER
if (createNetworkEvent)
{
Spawner.CreateNetworkEvent(item, remove: false);
}
#endif
foreach (XAttribute attribute in element.Attributes()) foreach (XAttribute attribute in element.Attributes())
{ {
if (!item.SerializableProperties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue; if (!item.properties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue;
bool shouldBeLoaded = false; bool shouldBeLoaded = false;
foreach (var propertyAttribute in property.Attributes.OfType<Serialize>()) foreach (var propertyAttribute in property.Attributes.OfType<Serialize>())
{ {
@@ -1937,7 +1902,7 @@ namespace Barotrauma
{ {
component.OnItemLoaded(); component.OnItemLoaded();
} }
return item; return item;
} }
@@ -91,7 +91,8 @@ namespace Barotrauma
public override void CreateNetworkEvent() public override void CreateNetworkEvent()
{ {
if (!Item.ItemList.Contains(container.Item)) int componentIndex = container.Item.GetComponentIndex(container);
if (componentIndex == -1)
{ {
string errorMsg = "Attempted to create a network event for an item (" + container.Item.Name + ") that hasn't been fully initialized yet."; string errorMsg = "Attempted to create a network event for an item (" + container.Item.Name + ") that hasn't been fully initialized yet.";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
@@ -100,13 +101,6 @@ namespace Barotrauma
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return; return;
} }
int componentIndex = container.Item.GetComponentIndex(container);
if (componentIndex == -1)
{
DebugConsole.Log("Creating a network event for the item \"" + container.Item + "\" failed, ItemContainer not found in components");
return;
}
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
@@ -28,10 +28,8 @@ namespace Barotrauma
public Explosion(float range, float force, float damage, float structureDamage, float empStrength = 0.0f) public Explosion(float range, float force, float damage, float structureDamage, float empStrength = 0.0f)
{ {
attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range) attack = new Attack(damage, 0.0f, 0.0f, structureDamage, range);
{ attack.SeverLimbsProbability = 1.0f;
SeverLimbsProbability = 1.0f
};
this.force = force; this.force = force;
this.empStrength = empStrength; this.empStrength = empStrength;
sparks = true; sparks = true;
@@ -185,6 +183,9 @@ namespace Barotrauma
Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false); Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false);
bool underWater = hull == null || explosionPos.Y < hull.Surface; bool underWater = hull == null || explosionPos.Y < hull.Surface;
Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false);
bool underWater = hull == null || explosionPos.Y < hull.Surface;
explosionPos = ConvertUnits.ToSimUnits(explosionPos); explosionPos = ConvertUnits.ToSimUnits(explosionPos);
Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>(); Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();
@@ -175,11 +175,12 @@ namespace Barotrauma
LimitSize(); LimitSize();
UpdateProjSpecific(growModifier); UpdateProjSpecific(growModifier);
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) #if CLIENT
{ if (GameMain.Client != null) return;
Remove(); #endif
}
if (size.X < 1.0f) Remove();
} }
partial void UpdateProjSpecific(float growModifier); partial void UpdateProjSpecific(float growModifier);
@@ -292,6 +293,10 @@ namespace Barotrauma
//evaporate some of the water //evaporate some of the water
hull.WaterVolume -= extinguishAmount; hull.WaterVolume -= extinguishAmount;
#if CLIENT
if (GameMain.Client != null) return;
#endif
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{ {
Remove(); Remove();
@@ -320,11 +325,12 @@ namespace Barotrauma
size.X -= extinguishAmount; size.X -= extinguishAmount;
hull.WaterVolume -= extinguishAmount; hull.WaterVolume -= extinguishAmount;
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)) #if CLIENT
{ if (GameMain.Client != null) return;
Remove(); #endif
}
if (size.X < 1.0f) Remove();
} }
public void Extinguish(float deltaTime, float amount, Vector2 worldPosition) public void Extinguish(float deltaTime, float amount, Vector2 worldPosition)
@@ -231,7 +231,7 @@ namespace Barotrauma
public static Level CreateRandom(LocationConnection locationConnection) public static Level CreateRandom(LocationConnection locationConnection)
{ {
string seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName; string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name;
float sizeFactor = MathUtils.InverseLerp( float sizeFactor = MathUtils.InverseLerp(
MapGenerationParams.Instance.SmallLevelConnectionLength, MapGenerationParams.Instance.SmallLevelConnectionLength,
@@ -1522,40 +1522,14 @@ namespace Barotrauma
outpost.MakeOutpost(); outpost.MakeOutpost();
Point? minSize = null; Point? minSize = null;
DockingPort subPort = null;
if (Submarine.MainSub != null) if (Submarine.MainSub != null)
{ {
Point subSize = Submarine.MainSub.GetDockedBorders().Size; Point subSize = Submarine.MainSub.GetDockedBorders().Size;
Point outpostSize = outpost.GetDockedBorders().Size; Point outpostSize = outpost.GetDockedBorders().Size;
minSize = new Point(Math.Max(subSize.X, outpostSize.X), subSize.Y + outpostSize.Y); minSize = new Point(Math.Max(subSize.X, outpostSize.X), subSize.Y + outpostSize.Y);
float closestDistance = float.MaxValue;
foreach (DockingPort port in DockingPort.List)
{
if (port.IsHorizontal || port.Docked) { continue; }
if (port.Item.Submarine != Submarine.MainSub) { continue; }
//the submarine port has to be at the top of the sub
if (port.Item.WorldPosition.Y < Submarine.MainSub.WorldPosition.Y) { continue; }
float dist = Math.Abs(port.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X);
if (dist < closestDistance)
{
subPort = port;
closestDistance = dist;
}
}
} }
float subDockingPortOffset = subPort == null ? 0.0f : subPort.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X; outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize));
//don't try to compensate if the port is very far from the sub's center of mass
if (Math.Abs(subDockingPortOffset) > 2000.0f)
{
subDockingPortOffset = MathHelper.Clamp(subDockingPortOffset, -2000.0f, 2000.0f);
string warningMsg = "Docking port very far from the sub's center of mass (submarine: " + Submarine.MainSub.Name + ", dist: " + subDockingPortOffset + "). The level generator may not be able to place the outpost so that docking is possible.";
DebugConsole.NewMessage(warningMsg, Color.Orange);
GameAnalyticsManager.AddErrorEventOnce("Lever.CreateOutposts:DockingPortVeryFar" + Submarine.MainSub.Name, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, warningMsg);
}
outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize, subDockingPortOffset));
if ((i == 0) == !Mirrored) if ((i == 0) == !Mirrored)
{ {
StartOutpost = outpost; StartOutpost = outpost;
@@ -15,6 +15,7 @@ namespace Barotrauma
public Vector3 Position; public Vector3 Position;
public float NetworkUpdateTimer; public float NetworkUpdateTimer;
public const float NetworkUpdateInterval = 0.2f;
public float Scale; public float Scale;
@@ -343,7 +343,7 @@ namespace Barotrauma
{ {
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { obj }); GameMain.NetworkMember.CreateEntityEvent(this, new object[] { obj });
obj.NeedsNetworkSyncing = false; obj.NeedsNetworkSyncing = false;
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval; obj.NetworkUpdateTimer = LevelObject.NetworkUpdateInterval;
} }
} }
@@ -432,16 +432,12 @@ namespace Barotrauma
{ {
if (ForceFluctuationStrength > 0.0f) if (ForceFluctuationStrength > 0.0f)
{ {
//no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers forceFluctuationTimer += deltaTime;
if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any()) if (forceFluctuationTimer > ForceFluctuationInterval)
{ {
forceFluctuationTimer += deltaTime; NeedsNetworkSyncing = true;
if (forceFluctuationTimer > ForceFluctuationInterval) currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
{ forceFluctuationTimer = 0.0f;
NeedsNetworkSyncing = true;
currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
forceFluctuationTimer = 0.0f;
}
} }
} }
@@ -15,8 +15,6 @@ namespace Barotrauma
public int TypeChangeTimer; public int TypeChangeTimer;
public string BaseName { get => baseName; }
public string Name { get; private set; } public string Name { get; private set; }
public Vector2 MapPosition { get; private set; } public Vector2 MapPosition { get; private set; }
@@ -33,10 +31,10 @@ namespace Barotrauma
get get
{ {
CheckMissionCompleted(); CheckMissionCompleted();
for (int i = availableMissions.Count; i < Connections.Count * 2; i++) for (int i = availableMissions.Count; i < Connections.Count * 2; i++)
{ {
int seed = (ToolBox.StringToInt(BaseName) + MissionsCompleted * 10 + i) % int.MaxValue; int seed = (ToolBox.StringToInt(Name) + MissionsCompleted * 10 + i) % int.MaxValue;
MTRandom rand = new MTRandom(seed); MTRandom rand = new MTRandom(seed);
LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count]; LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count];
@@ -47,7 +45,7 @@ namespace Barotrauma
if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; } if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; }
if (GameSettings.VerboseLogging && mission != null) if (GameSettings.VerboseLogging && mission != null)
{ {
DebugConsole.NewMessage("Generated a new mission for a location (location: " + Name + ", seed: " + seed.ToString("X") + ", missions completed: " + MissionsCompleted + ", type: " + mission.Name + ")", Color.White); DebugConsole.NewMessage("Generated a new mission for a location connection (seed: " + seed.ToString("X") + ", type: " + mission.Name + ")", Color.White);
} }
availableMissions.Add(mission); availableMissions.Add(mission);
} }
@@ -100,16 +98,7 @@ namespace Barotrauma
public void ChangeType(LocationType newType) public void ChangeType(LocationType newType)
{ {
if (newType == Type) { return; } if (newType == Type) return;
//clear missions from this and adjacent locations (they may be invalid now)
availableMissions.Clear();
foreach (LocationConnection connection in Connections)
{
connection.OtherLocation(this)?.availableMissions.Clear();
}
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
Type = newType; Type = newType;
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName); Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
@@ -121,7 +110,6 @@ namespace Barotrauma
{ {
if (mission.Completed) if (mission.Completed)
{ {
DebugConsole.Log("Mission \"" + mission.Name + "\" completed in \"" + Name + "\".");
MissionsCompleted++; MissionsCompleted++;
} }
} }
@@ -578,12 +578,10 @@ namespace Barotrauma
location.MissionsCompleted = missionsCompleted; location.MissionsCompleted = missionsCompleted;
if (showNotifications && prevLocationType != location.Type) if (showNotifications && prevLocationType != location.Type)
{ {
var change = prevLocationType.CanChangeTo.Find(c => ChangeLocationType(
c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant()); location,
if (change != null) prevLocationName,
{ prevLocationType.CanChangeTo.Find(c => c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant()));
ChangeLocationType(location, prevLocationName, change);
}
} }
break; break;
case "connection": case "connection":

Some files were not shown because too many files have changed in this diff Show More