(5c35a640e) Update tutorial-rework with dev

This commit is contained in:
Joonas Rikkonen
2019-03-27 20:52:47 +02:00
parent 47b16f4f9e
commit 080ac78ded
113 changed files with 3206 additions and 1298 deletions

View File

@@ -18,14 +18,13 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Animation\Ragdoll.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Attack.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Character.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterHealth.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\CharacterHealth.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterHUD.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterNetworking.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\CharacterSound.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Health\AfflictionHusk.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\HUDProgressBar.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Characters\Jobs\JobPrefab.cs" />

View File

@@ -31,6 +31,7 @@
<BootstrapperEnabled>true</BootstrapperEnabled>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<ApplicationIcon>..\BarotraumaShared\IconBmp.bmp</ApplicationIcon>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
@@ -191,6 +192,11 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Import Project="ClientCode.projitems" Label="Shared" />
<Import Project="..\BarotraumaShared\SharedCode.projitems" Label="Shared" />
<Import Project="..\BarotraumaShared\SharedContent.projitems" Label="Shared" />

View File

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.8.9.6")]
[assembly: AssemblyFileVersion("0.8.9.6")]
[assembly: AssemblyVersion("0.8.9.7")]
[assembly: AssemblyFileVersion("0.8.9.7")]

View File

@@ -14,6 +14,7 @@ float2 uvTopLeft;
float2 uvBottomRight;
float4 tintColor;
float4 solidColor;
struct VertexShaderInput
{
@@ -74,6 +75,11 @@ float4 mainPS(VertexShaderOutput input) : COLOR
return xTexture.Sample(TextureSampler, input.TexCoords) * input.Color;
}
float4 solidColorPS(VertexShaderOutput input) : COLOR
{
return solidColor * xTexture.Sample(TextureSampler, input.TexCoords).a;
}
technique DeformShader
{
pass Pass1
@@ -82,3 +88,12 @@ technique DeformShader
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();
}
}

View File

@@ -14,6 +14,7 @@ float2 uvTopLeft;
float2 uvBottomRight;
float4 tintColor;
float4 solidColor;
struct VertexShaderInput
{
@@ -74,6 +75,11 @@ float4 mainPS(VertexShaderOutput input) : COLOR
return xTexture.Sample(TextureSampler, input.TexCoords) * input.Color;
}
float4 solidColorPS(VertexShaderOutput input) : COLOR
{
return solidColor * xTexture.Sample(TextureSampler, input.TexCoords).a;
}
technique DeformShader
{
pass Pass1
@@ -82,3 +88,12 @@ technique DeformShader
PixelShader = compile ps_3_0 mainPS();
}
}
technique DeformShaderSolidColor
{
pass Pass1
{
VertexShader = compile vs_3_0 mainVS();
PixelShader = compile ps_3_0 solidColorPS();
}
}

View File

@@ -13,13 +13,39 @@ namespace Barotrauma
var pos = new Vector2(WorldPosition.X, -WorldPosition.Y);
if (soundRange > 0.0f)
{
Color color = Entity is Character ? Color.Yellow : Color.Orange;
Color color;
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, 3, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
}
if (sightRange > 0.0f)
{
Color color = Entity is Character ? Color.CornflowerBlue : Color.CadetBlue;
Color color;
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, 6, 8, color, thickness: 2 / Screen.Selected.Cam.Zoom);
}
}
}

View File

@@ -21,13 +21,11 @@ namespace Barotrauma
if (wallTarget != null)
{
Vector2 wallTargetPos = wallTarget.Position;
if (wallTarget.Structure.Submarine != null) wallTargetPos += wallTarget.Structure.Submarine.Position;
wallTargetPos.Y = -wallTargetPos.Y;
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.Font.DrawString(spriteBatch, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.ToString()})", pos - Vector2.UnitY * 20.0f, Color.Red);
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity.ToString()} ({targetValue.FormatZeroDecimal()})", Color.Red, Color.Black);
}
/*GUI.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, Color.Red);
@@ -70,29 +68,34 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, pos, pos + ConvertUnits.ToDisplayUnits(new Vector2(Steering.X, -Steering.Y)), Color.Blue, width: 3);
if (latchOntoAI.WallAttachPos.HasValue)
{
GUI.DrawLine(spriteBatch, pos,
ConvertUnits.ToDisplayUnits(new Vector2(latchOntoAI.WallAttachPos.Value.X, -latchOntoAI.WallAttachPos.Value.Y)), Color.Green, 0, 3);
}
}
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++)
if (steeringManager is IndoorsSteeringManager pathSteering)
{
GUI.DrawLine(spriteBatch,
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),
Color.Orange * 0.6f, 0, 3);
var path = pathSteering.CurrentPath;
if (path != null)
{
if (path.CurrentNode != null)
{
GUI.DrawLine(spriteBatch, pos,
new Vector2(path.CurrentNode.DrawPosition.X, -path.CurrentNode.DrawPosition.Y),
Color.DarkViolet, 0, 3);
GUI.SmallFont.DrawString(spriteBatch,
pathSteering.CurrentPath.Nodes[i].ID.ToString(),
new Vector2(pathSteering.CurrentPath.Nodes[i].DrawPosition.X, -pathSteering.CurrentPath.Nodes[i].DrawPosition.Y - 10),
Color.LightGreen);
GUI.DrawString(spriteBatch, pos - new Vector2(0, 100), "Path cost: " + path.Cost.FormatZeroDecimal(), Color.White, Color.Black * 0.5f);
}
for (int i = 1; i < path.Nodes.Count; i++)
{
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(Steering.X, -Steering.Y)), Color.Blue, width: 3);

View File

@@ -75,11 +75,12 @@ namespace Barotrauma
GUI.SmallFont.DrawString(spriteBatch,
currentNode.ID.ToString(),
new Vector2(currentNode.DrawPosition.X, -currentNode.DrawPosition.Y - 10),
Color.LightGreen);
new Vector2(currentNode.DrawPosition.X + 20, -currentNode.DrawPosition.Y - 20),
Color.SkyBlue);
}
}
}
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);
//if (Character.IsKeyDown(InputType.Aim))

View File

@@ -210,7 +210,9 @@ namespace Barotrauma
else
{
Vector2 positionError = serverPos.Position - localPos.Position;
float rotationError = serverPos.Rotation - localPos.Rotation;
float rotationError = serverPos.Rotation.HasValue && localPos.Rotation.HasValue ?
serverPos.Rotation.Value - localPos.Rotation.Value :
0.0f;
for (int i = localPosIndex; i < character.MemLocalState.Count; i++)
{

View File

@@ -214,8 +214,8 @@ namespace Barotrauma
msg.ReadRangedSingle(-MaxVel, MaxVel, 12));
bool fixedRotation = msg.ReadBoolean();
float rotation = AnimController.Collider.Rotation;
float angularVelocity = AnimController.Collider.AngularVelocity;
float? rotation = null;
float? angularVelocity = null;
if (!fixedRotation)
{
rotation = msg.ReadFloat();

View File

@@ -403,6 +403,11 @@ namespace Barotrauma
AssignRelayToServer("help", false);
AssignRelayToServer("verboselogging", false);
AssignRelayToServer("freecam", false);
#if DEBUG
AssignRelayToServer("simulatedlatency", false);
AssignRelayToServer("simulatedloss", false);
AssignRelayToServer("simulatedduplicateschance", false);
#endif
commands.Add(new Command("clientlist", "", (string[] args) => { }));
AssignRelayToServer("clientlist", true);
@@ -439,7 +444,7 @@ namespace Barotrauma
AssignOnExecute("ambientlight", (string[] args) =>
{
Color color = XMLExtensions.ParseColor(string.Join("", args));
Color color = XMLExtensions.ParseColor(string.Join(",", args));
if (Level.Loaded != null)
{
Level.Loaded.GenerationParams.AmbientLightColor = color;
@@ -528,6 +533,28 @@ namespace Barotrauma
}
}, isCheat: true));
commands.Add(new Command("resetall", "Reset all items and structures to prefabs. Only applicable in the subeditor.", args =>
{
if (Screen.Selected == GameMain.SubEditorScreen)
{
Item.ItemList.ForEach(i => i.Reset());
Structure.WallList.ForEach(s => s.Reset());
foreach (MapEntity entity in MapEntity.SelectedList)
{
if (entity is Item item)
{
item.CreateEditingHUD();
break;
}
else if (entity is Structure structure)
{
structure.CreateEditingHUD();
break;
}
}
}
}));
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)
@@ -3786,6 +3813,201 @@ namespace Barotrauma
NPCConversation.WriteToCSV();
}));
commands.Add(new Command("csvtoxml", "csvtoxml [language] -> Converts .csv localization files in Content/NPCConversations & Content/Texts to .xml for use in-game.", (string[] args) =>
{
if (args.Length == 0) return;
LocalizationCSVtoXML.Convert(args[0]);
}));
#endif
commands.Add(new Command("cleanbuild", "", (string[] args) =>
{
GameMain.Config.MusicVolume = 0.5f;
GameMain.Config.SoundVolume = 0.5f;
NewMessage("Music and sound volume set to 0.5", Color.Green);
commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) =>
{
float defaultZoom = Screen.Selected.Cam.DefaultZoom;
if (args.Length > 0) float.TryParse(args[0], NumberStyles.Number, CultureInfo.InvariantCulture, out defaultZoom);
float zoomSmoothness = Screen.Selected.Cam.ZoomSmoothness;
if (args.Length > 1) float.TryParse(args[1], NumberStyles.Number, CultureInfo.InvariantCulture, out zoomSmoothness);
float moveSmoothness = Screen.Selected.Cam.MoveSmoothness;
if (args.Length > 2) float.TryParse(args[2], NumberStyles.Number, CultureInfo.InvariantCulture, out moveSmoothness);
float minZoom = Screen.Selected.Cam.MinZoom;
if (args.Length > 3) float.TryParse(args[3], NumberStyles.Number, CultureInfo.InvariantCulture, out minZoom);
float maxZoom = Screen.Selected.Cam.MaxZoom;
if (args.Length > 4) float.TryParse(args[4], NumberStyles.Number, CultureInfo.InvariantCulture, out maxZoom);
Screen.Selected.Cam.DefaultZoom = defaultZoom;
Screen.Selected.Cam.ZoomSmoothness = zoomSmoothness;
Screen.Selected.Cam.MoveSmoothness = moveSmoothness;
Screen.Selected.Cam.MinZoom = minZoom;
Screen.Selected.Cam.MaxZoom = maxZoom;
}));
GameMain.Config.SaveNewPlayerConfig();
commands.Add(new Command("refreshrect", "Updates the dimensions of the selected items to match the ones defined in the prefab. Applied only in the subeditor.", (string[] args) =>
{
//TODO: maybe do this automatically during loading when possible?
if (Screen.Selected == GameMain.SubEditorScreen)
{
if (!MapEntity.SelectedAny)
{
ThrowError("You have to select item(s) first!");
}
else
{
foreach (var mapEntity in MapEntity.SelectedList)
{
if (mapEntity is Item item)
{
item.Rect = new Rectangle(item.Rect.X, item.Rect.Y,
(int)(item.Prefab.sprite.size.X * item.Prefab.Scale),
(int)(item.Prefab.sprite.size.Y * item.Prefab.Scale));
}
else if (mapEntity is Structure structure)
{
if (!structure.ResizeHorizontal)
{
structure.Rect = new Rectangle(structure.Rect.X, structure.Rect.Y,
(int)structure.Prefab.ScaledSize.X,
structure.Rect.Height);
}
if (!structure.ResizeVertical)
{
structure.Rect = new Rectangle(structure.Rect.X, structure.Rect.Y,
structure.Rect.Width,
(int)structure.Prefab.ScaledSize.Y);
}
}
}
}
}
}, isCheat: false));
#endif
commands.Add(new Command("dumptexts", "dumptexts [filepath]: Extracts all the texts from the given text xml and writes them into a file (using the same filename, but with the .txt extension). If the filepath is omitted, the EnglishVanilla.xml file is used.", (string[] args) =>
{
string filePath = args.Length > 0 ? args[0] : "Content/Texts/EnglishVanilla.xml";
var doc = XMLExtensions.TryLoadXml(filePath);
if (doc?.Root == null) return;
List<string> lines = new List<string>();
foreach (XElement element in doc.Root.Elements())
{
lines.Add(element.ElementInnerText());
}
File.WriteAllLines(Path.GetFileNameWithoutExtension(filePath) + ".txt", lines);
},
() =>
{
var files = TextManager.GetTextFiles().Select(f => f.Replace("\\", "/"));
return new string[][]
{
TextManager.GetTextFiles().Where(f => Path.GetExtension(f)==".xml").ToArray()
};
}));
commands.Add(new Command("loadtexts", "loadtexts [sourcefile] [destinationfile]: Loads all lines of text from a given .txt file and inserts them sequientially into the elements of an xml file. If the file paths are omitted, EnglishVanilla.txt and EnglishVanilla.xml are used.", (string[] args) =>
{
string sourcePath = args.Length > 0 ? args[0] : "Content/Texts/EnglishVanilla.txt";
string destinationPath = args.Length > 1 ? args[1] : "Content/Texts/EnglishVanilla.xml";
string[] lines;
try
{
lines = File.ReadAllLines(sourcePath);
}
catch (Exception e)
{
ThrowError("Reading the file \"" + sourcePath + "\" failed.", e);
return;
}
var doc = XMLExtensions.TryLoadXml(destinationPath);
int i = 0;
foreach (XElement element in doc.Root.Elements())
{
if (i >= lines.Length)
{
ThrowError("Error while loading texts to the xml file. The xml has more elements than the number of lines in the text file.");
return;
}
element.Value = lines[i];
i++;
}
doc.Save(destinationPath);
},
() =>
{
var files = TextManager.GetTextFiles().Select(f => f.Replace("\\", "/"));
return new string[][]
{
files.Where(f => Path.GetExtension(f)==".txt").ToArray(),
files.Where(f => Path.GetExtension(f)==".xml").ToArray()
};
}));
commands.Add(new Command("updatetextfile", "updatetextfile [sourcefile] [destinationfile]: Inserts all the xml elements that are only present in the source file into the destination file. Can be used to update outdated translation files more easily.", (string[] args) =>
{
if (args.Length < 2) return;
string sourcePath = args[0];
string destinationPath = args[1];
var sourceDoc = XMLExtensions.TryLoadXml(sourcePath);
var destinationDoc = XMLExtensions.TryLoadXml(destinationPath);
XElement destinationElement = destinationDoc.Root.Elements().First();
foreach (XElement element in sourceDoc.Root.Elements())
{
if (destinationDoc.Root.Element(element.Name) == null)
{
element.Value = "!!!!!!!!!!!!!" + element.Value;
destinationElement.AddAfterSelf(element);
}
XNode nextNode = destinationElement.NextNode;
while ((!(nextNode is XElement) || nextNode == element) && nextNode != null) nextNode = nextNode.NextNode;
destinationElement = nextNode as XElement;
}
destinationDoc.Save(destinationPath);
},
() =>
{
var files = TextManager.GetTextFiles().Where(f => Path.GetExtension(f) == ".xml").Select(f => f.Replace("\\", "/")).ToArray();
return new string[][]
{
files,
files
};
}));
commands.Add(new Command("dumpentitytexts", "dumpentitytexts [filepath]: gets the names and descriptions of all entity prefabs and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EntityTexts.txt", (string[] args) =>
{
string filePath = args.Length > 0 ? args[0] : "Content/Texts/EntityTexts.txt";
List<string> lines = new List<string>();
foreach (MapEntityPrefab me in MapEntityPrefab.List)
{
lines.Add("<EntityName." + me.Identifier + ">" + me.Name + "</" + me.Identifier + ".Name>");
lines.Add("<EntityDescription." + me.Identifier + ">" + me.Description + "</" + me.Identifier + ".Description>");
}
File.WriteAllLines(filePath, lines);
}));
#if DEBUG
commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) =>
{
if (args.Length != 1) return;
TextManager.CheckForDuplicates(args[0]);
}));
commands.Add(new Command("writetocsv", "Writes the default language (English) to a .csv file.", (string[] args) =>
{
TextManager.WriteToCSV();
NPCConversation.WriteToCSV();
}));
commands.Add(new Command("csvtoxml", "csvtoxml [language] -> Converts .csv localization files in Content/NPCConversations & Content/Texts to .xml for use in-game.", (string[] args) =>
{
if (args.Length == 0) return;

View File

@@ -663,20 +663,6 @@ namespace Barotrauma
msg.Timer -= deltaTime;
msg.Pos += msg.Velocity * deltaTime;
}
foreach (GUIMessage msg in messages)
{
if (!msg.WorldSpace) continue;
msg.Timer -= deltaTime;
msg.Pos += msg.Velocity * deltaTime;
}
foreach (GUIMessage msg in messages)
{
if (!msg.WorldSpace) continue;
msg.Timer -= deltaTime;
msg.Pos += msg.Velocity * deltaTime;
}
}
messages.RemoveAll(m => m.Timer <= 0.0f);

View File

@@ -12,7 +12,7 @@ namespace Barotrauma
{
private UInt16 startWatchmanID, endWatchmanID;
public static GUIComponent StartCampaignSetup(IEnumerable<string> saveFiles)
public static GUIComponent StartCampaignSetup( IEnumerable<Submarine> submarines, IEnumerable<string> saveFiles)
{
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 loadCampaignContainer = new GUIFrame(new RectTransform(Vector2.One, campaignContainer.RectTransform, Anchor.BottomLeft), style: null);
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, saveFiles);
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, submarines, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign"))
@@ -93,7 +93,6 @@ namespace Barotrauma
}
}
protected override void WatchmanInteract(Character watchman, Character interactor)
{
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
@@ -107,8 +106,7 @@ namespace Barotrauma
return;
}
if (GameMain.Client != null && interactor == Character.Controlled &&
(GameMain.Client.HasPermission(ClientPermissions.ManageRound) || GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)))
if (GameMain.Client != null && interactor == Character.Controlled)
{
var msgBox = new GUIMessageBox("", TextManager.Get("CampaignEnterOutpostPrompt")
.Replace("[locationname]", Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
@@ -123,7 +121,6 @@ namespace Barotrauma
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
}

View File

@@ -47,7 +47,7 @@ namespace Barotrauma
//if "%" is found
if (index > 0)
{
while (true)
while (index > 0)
{
//search for end of label
index -= 1;
@@ -439,7 +439,6 @@ namespace Barotrauma
}
};
#endif
var radioButtonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), voiceSettings.RectTransform))
{
Stretch = true,
@@ -458,6 +457,24 @@ namespace Barotrauma
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));
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),
@@ -469,7 +486,6 @@ namespace Barotrauma
voiceKeyBox.SelectedColor = Color.Gold * 0.3f;
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"))
{
TextGetter = () =>
@@ -509,13 +525,13 @@ namespace Barotrauma
if (GameMain.Client == null && VoipCapture.Instance == null)
{
VoipCapture.Create(GameMain.Config.VoiceCaptureDevice);
}
if (VoipCapture.Instance == null)
{
VoiceSetting = vMode = VoiceMode.Disabled;
voiceInputContainer.Visible = false;
voiceActivityGroup.Visible = false;
return;
if (VoipCapture.Instance == null)
{
VoiceSetting = vMode = VoiceMode.Disabled;
voiceInputContainer.Visible = false;
voiceActivityGroup.Visible = false;
return;
}
}
}
else

View File

@@ -93,7 +93,7 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing)
{
Color color = Color.White;
Color color = item.SpriteColor;
if (brokenSprite == null)
{
//broken doors turn black if no broken sprite has been configured
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
weldSpritePos.Y = -weldSpritePos.Y;
weldedSprite.Draw(spriteBatch,
weldSpritePos, Color.White * (stuck / 100.0f), scale: item.Scale);
weldSpritePos, item.SpriteColor * (stuck / 100.0f), scale: item.Scale);
}
if (openState == 1.0f)

View File

@@ -313,7 +313,7 @@ namespace Barotrauma.Items.Components
if (sound == null) { return 0.0f; }
if (sound.VolumeProperty == "") { return sound.VolumeMultiplier; }
if (properties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
{
float newVolume = 0.0f;
try

View File

@@ -331,8 +331,18 @@ namespace Barotrauma.Items.Components
spriteBatch.GraphicsDevice.ScissorRectangle = container.Rect;
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,
fissionRateMeter, FissionRate, new Vector2(0.0f, 100.0f), optimalFissionRate, allowedFissionRate);
fissionRateMeter, FissionRate + jitter, new Vector2(0.0f, 100.0f), optimalFissionRate, allowedFissionRate);
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
@@ -392,12 +402,12 @@ namespace Barotrauma.Items.Components
Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale);
Vector2 optimalRangeNormalized = new Vector2(
(optimalRange.X - range.X) / (range.Y - range.X),
(optimalRange.Y - range.X) / (range.Y - range.X));
MathHelper.Clamp((optimalRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
MathHelper.Clamp((optimalRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
Vector2 allowedRangeNormalized = new Vector2(
(allowedRange.X - range.X) / (range.Y - range.X),
(allowedRange.Y - range.X) / (range.Y - range.X));
MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
Vector2 sectorRad = new Vector2(-1.57f, 1.57f);
@@ -417,10 +427,10 @@ namespace Barotrauma.Items.Components
{
spriteBatch.End();
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0,0,GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale));
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3);
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
sectorSprite.Draw(spriteBatch, pos, Color.LightGreen, MathHelper.PiOver2, scale);
sectorSprite.Draw(spriteBatch, pos, Color.LightGreen, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale);
sectorSprite.Draw(spriteBatch, pos, Color.Orange, optimalSectorRad.X, scale);
sectorSprite.Draw(spriteBatch, pos, Color.Red, allowedSectorRad.X, scale);

View File

@@ -785,8 +785,9 @@ namespace Barotrauma.Items.Components
foreach (Limb limb in c.AnimController.Limbs)
{
float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared();
if (!limb.body.Enabled) { continue; }
float pointDist = ((limb.WorldPosition - pingSource) * displayScale).LengthSquared();
if (limb.SimPosition == Vector2.Zero || pointDist > DisplayRadius * DisplayRadius) continue;
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)

View File

@@ -1,4 +1,6 @@
using Barotrauma.Particles;
using Barotrauma.Networking;
using Barotrauma.Particles;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
@@ -112,8 +114,7 @@ namespace Barotrauma.Items.Components
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)
{
Skill skill = c.UserData as Skill;
if (skill == null) continue;
if (!(c.UserData is Skill skill)) continue;
GUITextBlock textBlock = (GUITextBlock)c;
if (character.GetSkillLevel(skill.Identifier) < skill.Level)
@@ -126,5 +127,15 @@ 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
}
}
}

View File

@@ -181,7 +181,7 @@ namespace Barotrauma.Items.Components
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in allAfflictions)
{
if (affliction.Strength < affliction.Prefab.ActivationThreshold || affliction.Strength <= 0.0f) continue;
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) continue;
if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
{
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;

View File

@@ -237,13 +237,13 @@ namespace Barotrauma.Items.Components
railSprite?.Draw(spriteBatch,
drawPos,
Color.White,
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
barrelSprite?.Draw(spriteBatch,
drawPos - new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset * item.Scale,
Color.White,
item.SpriteColor,
rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
@@ -288,7 +288,10 @@ namespace Barotrauma.Items.Components
availableAmmo.AddRange(itemContainer.Inventory.Items);
}
float chargeRate = powerConsumption <= 0.0f ? 1.0f : batteryCharge / batteryCapacity;
float chargeRate =
powerConsumption <= 0.0f ?
1.0f :
batteryCapacity > 0.0f ? batteryCharge / batteryCapacity : 0.0f;
bool charged = batteryCharge * 3600.0f > powerConsumption;
bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null);
if (ShowChargeIndicator && PowerConsumption > 0.0f)

View File

@@ -448,7 +448,7 @@ namespace Barotrauma
}
}
private GUIComponent CreateEditingHUD(bool inGame = false)
public 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 };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null)
@@ -457,40 +457,42 @@ namespace Barotrauma
};
var itemEditor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true);
if (!inGame && 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())
{
itemsText.Text += TextManager.Get("None");
}
else
{
for (int i = 0; i < AllowedLinks.Count; i++)
{
itemsText.Text += AllowedLinks[i];
if (i < AllowedLinks.Count - 1)
{
itemsText.Text += ", ";
}
}
}
itemEditor.AddCustomContent(linkText, 1);
itemEditor.AddCustomContent(itemsText, 2);
linkText.TextColor = Color.Yellow;
itemsText.TextColor = Color.Yellow;
}
if (!inGame)
{
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())
{
itemsText.Text += TextManager.Get("None");
}
};
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
for (int i = 0; i < AllowedLinks.Count; i++)
{
itemsText.Text += AllowedLinks[i];
if (i < AllowedLinks.Count - 1)
{
itemsText.Text += ", ";
}
}
}
itemEditor.AddCustomContent(linkText, 1);
itemEditor.AddCustomContent(itemsText, 2);
linkText.TextColor = Color.Yellow;
itemsText.TextColor = Color.Yellow;
}
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, 20)), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.02f
};
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
@@ -499,7 +501,7 @@ namespace Barotrauma
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
@@ -510,7 +512,7 @@ namespace Barotrauma
};
if (Sprite != null)
{
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"));
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"));
reloadTextureButton.OnClicked += (button, data) =>
{
Sprite.ReloadXML();
@@ -518,6 +520,15 @@ namespace Barotrauma
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);
}

View File

@@ -1,14 +1,13 @@
using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using System.Linq;
using Lidgren.Network;
namespace Barotrauma
{
@@ -19,6 +18,8 @@ namespace Barotrauma
private List<Decal> decals = new List<Decal>();
private float serverUpdateDelay;
private float remoteWaterVolume, remoteOxygenPercentage;
private List<Vector3> remoteFireSources;
private bool networkUpdatePending;
private float networkUpdateTimer;
@@ -139,6 +140,10 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
serverUpdateDelay -= deltaTime;
if (serverUpdateDelay <= 0.0f)
{
ApplyRemoteState();
}
if (networkUpdatePending)
{
@@ -547,18 +552,18 @@ namespace Barotrauma
public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime)
{
float newWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
float newOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
bool hasFireSources = message.ReadBoolean();
int fireSourceCount = 0;
List<Vector3> newFireSources = new List<Vector3>();
remoteFireSources = new List<Vector3>();
if (hasFireSources)
{
fireSourceCount = message.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++)
{
newFireSources.Add(new Vector3(
remoteFireSources.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),
message.ReadRangedSingle(0.0f, 1.0f, 8)));
@@ -567,41 +572,6 @@ namespace Barotrauma
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();
}

View File

@@ -405,11 +405,14 @@ namespace Barotrauma.Lights
}
if (highlightedEntities.Count == 0) { return false; }
//draw characters in solid white first
//draw characters in light blue first
graphics.SetRenderTarget(HighlightMap);
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.LightBlue.ToVector4());
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);
foreach (Entity highlighted in highlightedEntities)
{
@@ -426,9 +429,12 @@ namespace Barotrauma.Lights
//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
SolidColorEffect.Parameters["color"].SetValue(Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase));
Vector4 overlayColor = Color.Black.ToVector4() * MathHelper.Lerp(0.5f, 0.9f, phase);
SolidColorEffect.Parameters["color"].SetValue(overlayColor);
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColorBlur"];
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);
foreach (Entity highlighted in highlightedEntities)
{
@@ -450,6 +456,8 @@ 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.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
return true;
}
@@ -471,6 +479,10 @@ namespace Barotrauma.Lights
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
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.Gray.ToVector4());
@@ -487,6 +499,9 @@ namespace Barotrauma.Lights
}
spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
graphics.SetRenderTarget(null);
graphics.BlendState = BlendState.AlphaBlend;
}

View File

@@ -98,7 +98,7 @@ namespace Barotrauma
Stretch = true,
RelativeSpacing = 0.02f
};
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
@@ -107,7 +107,7 @@ namespace Barotrauma
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
@@ -116,7 +116,7 @@ namespace Barotrauma
return true;
}
};
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"))
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"))
{
OnClicked = (button, data) =>
{
@@ -125,6 +125,15 @@ namespace Barotrauma
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);
PositionEditingHUD();
@@ -351,6 +360,7 @@ namespace Barotrauma
-Bodies[i].Rotation, Color.White);
}
}
AiTarget?.Draw(spriteBatch);
}
}

View File

@@ -132,6 +132,7 @@ namespace Barotrauma.Networking
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, inGameHUD.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
AbsoluteSpacing = 5,
CanBeFocused = false
};
@@ -147,9 +148,10 @@ namespace Barotrauma.Networking
Visible = false
};
EndVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.6f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
EndVoteTickBox = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.4f), buttonContainer.RectTransform) { MinSize = new Point(150, 0) },
TextManager.Get("EndRound"))
{
UserData = TextManager.Get("EndRound"),
OnSelected = ToggleEndRoundVote,
Visible = false
};
@@ -323,18 +325,17 @@ namespace Barotrauma.Networking
// Loop until we are approved
//TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly)
string connectingText = TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP);
string connectingText = TextManager.Get("Connecting");
while (!CanStart && !connectCancelled)
{
if (reconnectBox == null)
{
reconnectBox = new GUIMessageBox(TextManager.Get("Connecting"), connectingText, new string[] { TextManager.Get("Cancel") });
reconnectBox = new GUIMessageBox(connectingText, TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP), new string[] { TextManager.Get("Cancel") });
reconnectBox.Buttons[0].OnClicked += CancelConnect;
reconnectBox.Buttons[0].OnClicked += reconnectBox.Close;
}
reconnectBox.Text.Text = connectingText + new string('.', ((int)Timing.TotalTime % 3 + 1));
reconnectBox.Header.Text = connectingText + new string('.', ((int)Timing.TotalTime % 3 + 1));
if (DateTime.Now > reqAuthTime)
{
@@ -621,7 +622,7 @@ namespace Barotrauma.Networking
if (gameStarted) SetRadioButtonColor();
if (ShowNetStats)
if (ShowNetStats && client?.ServerConnection != null)
{
netStats.AddValue(NetStats.NetStatType.ReceivedBytes, client.ServerConnection.Statistics.ReceivedBytes);
netStats.AddValue(NetStats.NetStatType.SentBytes, client.ServerConnection.Statistics.SentBytes);
@@ -782,7 +783,7 @@ namespace Barotrauma.Networking
saveFiles.Add(inc.ReadString());
}
GameMain.NetLobbyScreen.CampaignSetupUI = MultiPlayerCampaign.StartCampaignSetup(saveFiles);
GameMain.NetLobbyScreen.CampaignSetupUI = MultiPlayerCampaign.StartCampaignSetup(serverSubmarines, saveFiles);
break;
case ServerPacketHeader.PERMISSIONS:
ReadPermissions(inc);
@@ -835,6 +836,8 @@ namespace Barotrauma.Networking
DisconnectReason disconnectReason = DisconnectReason.Unknown;
if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason);
DebugConsole.NewMessage("Received a disconnect message (" + disconnectMsg + ")");
if (disconnectReason == DisconnectReason.ServerFull)
{
//already waiting for a slot to free up, do nothing
@@ -864,22 +867,19 @@ namespace Barotrauma.Networking
waitInServerQueueBox = null;
CoroutineManager.StopCoroutines("WaitInServerQueue");
}
else
{
string msg = "";
if (disconnectReason == DisconnectReason.Unknown)
{
msg = disconnectMsg;
}
else
{
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
if (allowReconnect && disconnectReason == DisconnectReason.Unknown)
{
DebugConsole.NewMessage("Attempting to reconnect...");
string msg = TextManager.GetServerMessage(disconnectMsg);
msg = string.IsNullOrWhiteSpace(msg) ?
TextManager.Get("ConnectionLostReconnecting") :
msg + '\n' + TextManager.Get("ConnectionLostReconnecting");
reconnectBox = new GUIMessageBox(
TextManager.Get("ConnectionLost"),
TextManager.Get("ConnectionLostReconnecting"), new string[0]);
msg, new string[0]);
connected = false;
ConnectToServer(serverIP);
}
@@ -888,10 +888,12 @@ namespace Barotrauma.Networking
string msg = "";
if (disconnectReason == DisconnectReason.Unknown)
{
DebugConsole.NewMessage("Do not attempt reconnect (not allowed).");
msg = disconnectMsg;
}
else
{
DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection).");
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
for (int i = 1; i < splitMsg.Length; i++)
@@ -1031,9 +1033,10 @@ namespace Barotrauma.Networking
EndVoteTickBox.Selected = false;
int seed = inc.ReadInt32();
string levelSeed = inc.ReadString();
float levelDifficulty = inc.ReadFloat();
int seed = inc.ReadInt32();
string levelSeed = inc.ReadString();
int levelEqualityCheckVal = inc.ReadInt32();
float levelDifficulty = inc.ReadFloat();
byte losMode = inc.ReadByte();
@@ -1102,6 +1105,15 @@ namespace Barotrauma.Networking
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);
gameStarted = true;
@@ -1171,8 +1183,8 @@ namespace Barotrauma.Networking
}
}
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, submarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, submarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.SubList, serverSubmarines);
GameMain.NetLobbyScreen.UpdateSubList(GameMain.NetLobbyScreen.ShuttleList.ListBox, serverSubmarines);
gameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
@@ -1458,6 +1470,9 @@ namespace Barotrauma.Networking
case ServerNetObject.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.CLIENT_LIST:
ReadClientList(inc);
break;
case ServerNetObject.CHAT_MESSAGE:
ChatMessage.ClientRead(inc);
break;
@@ -1781,6 +1796,8 @@ namespace Barotrauma.Networking
SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession);
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
//(as there may have been campaign updates after the save file was created)
campaign.LastUpdateID--;
@@ -1798,7 +1815,7 @@ namespace Barotrauma.Networking
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);
}
@@ -2067,7 +2084,8 @@ namespace Barotrauma.Networking
msg.Write(true); msg.WritePadBits();
msg.Write(savePath);
msg.Write(mapSeed);
msg.Write(sub.FilePath);
msg.Write(sub.Name);
msg.Write(sub.MD5Hash.Hash);
client.SendMessage(msg, NetDeliveryMethod.ReliableUnordered);
@@ -2305,13 +2323,25 @@ namespace Barotrauma.Networking
if (EndVoteCount > 0)
{
string endVoteText = TextManager.Get("EndRoundVotes")
.Replace("[y]", EndVoteCount.ToString())
.Replace("[n]", (EndVoteMax - EndVoteCount).ToString());
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 10.0f - GUI.SmallFont.MeasureString(endVoteText).X, 40),
endVoteText,
Color.White,
font: GUI.SmallFont);
if (EndVoteTickBox.Visible)
{
EndVoteTickBox.Text =
(EndVoteTickBox.UserData as string) + " " + EndVoteCount + "/" + EndVoteMax;
}
else
{
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)

View File

@@ -1,4 +1,5 @@
using Lidgren.Network;
using Microsoft.Xna.Framework;
using OpenTK.Audio.OpenAL;
using System;
using System.Linq;
@@ -27,6 +28,11 @@ namespace Barotrauma.Networking
private set;
}
public float Gain
{
get { return GameMain.Config?.MicrophoneVolume ?? 1.0f; }
}
public DateTime LastEnqueueAudio;
public override byte QueueID
@@ -69,6 +75,7 @@ namespace Barotrauma.Networking
{
if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound"))
{
GUI.SettingsMenuOpen = false;
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("VoipCaptureDeviceNotFound"))
{
UserData = "capturedevicenotfound"
@@ -160,7 +167,8 @@ namespace Barotrauma.Networking
double maxAmplitude = 0.0f;
for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++)
{
double sampleVal = (double)uncompressedBuffer[i] / (double)short.MaxValue;
uncompressedBuffer[i] = (short)MathHelper.Clamp((uncompressedBuffer[i] * Gain), -short.MaxValue, short.MaxValue);
double sampleVal = uncompressedBuffer[i] / (double)short.MaxValue;
maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
}
double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0);

View File

@@ -150,10 +150,10 @@ namespace Barotrauma
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
Vector2 newPosition = SimPosition;
float newRotation = Rotation;
float? newRotation = null;
bool awake = body.Awake;
Vector2 newVelocity = LinearVelocity;
float newAngularVelocity = AngularVelocity;
float? newAngularVelocity = null;
newPosition = new Vector2(
msg.ReadFloat(),
@@ -179,12 +179,12 @@ namespace Barotrauma
msg.ReadPadBits();
if (!MathUtils.IsValid(newPosition) ||
!MathUtils.IsValid(newRotation) ||
!MathUtils.IsValid(newVelocity) ||
!MathUtils.IsValid(newAngularVelocity))
(newRotation.HasValue && !MathUtils.IsValid(newRotation.Value)) ||
(newAngularVelocity.HasValue && !MathUtils.IsValid(newAngularVelocity.Value)))
{
string errorMsg = "Received invalid position data for \"" + parentDebugName
+ "\" (position: " + newPosition + ", rotation: " + newRotation + ", velocity: " + newVelocity + ", angular velocity: " + newAngularVelocity + ")";
+ "\" (position: " + newPosition + ", rotation: " + (newRotation ?? 0) + ", velocity: " + newVelocity + ", angular velocity: " + (newAngularVelocity ?? 0) + ")";
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif

View File

@@ -33,7 +33,7 @@ namespace Barotrauma
private readonly bool isMultiplayer;
public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<string> saveFiles=null)
public CampaignSetupUI(bool isMultiplayer, GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<Submarine> submarines, IEnumerable<string> saveFiles = null)
{
this.isMultiplayer = isMultiplayer;
this.newGameContainer = newGameContainer;
@@ -45,6 +45,12 @@ namespace Barotrauma
RelativeSpacing = 0.05f
};
var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform))
{
Stretch = true,
@@ -63,7 +69,7 @@ namespace Barotrauma
OnSelected = CheckForPax
};
UpdateSubList();
UpdateSubList(submarines);
// New game right sideon
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), rightColumn.RectTransform), TextManager.Get("SaveName") + ":", textAlignment: Alignment.BottomLeft);
@@ -90,8 +96,7 @@ namespace Barotrauma
return false;
}
Submarine selectedSub = subList.SelectedData as Submarine;
if (selectedSub == null) return false;
if (!(subList.SelectedData is Submarine selectedSub)) { return false; }
if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
{
@@ -199,9 +204,9 @@ namespace Barotrauma
public void UpdateSubList(IEnumerable<Submarine> submarines)
{
#if DEBUG
var subsToShow = Submarine.SavedSubmarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus));
var subsToShow = submarines.Where(s => !s.HasTag(SubmarineTag.HideInMenus));
#else
var subsToShow = Submarine.SavedSubmarines;
var subsToShow = submarines;
#endif
subList.ClearChildren();

View File

@@ -459,6 +459,12 @@ namespace Barotrauma
OnClicked = (GUIButton btn, object obj) => { StartRound?.Invoke(); return 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);
@@ -502,7 +508,13 @@ namespace Barotrauma
CanBeFocused = false
};
if (startButton != null) { startButton.Enabled = true; }
if (startButton != null)
{
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)

View File

@@ -282,7 +282,7 @@ namespace Barotrauma
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);
campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame)
campaignSetupUI = new CampaignSetupUI(false, paddedNewGame, paddedLoadGame, Submarine.SavedSubmarines)
{
LoadGame = LoadGame,
StartNewGame = StartGame
@@ -336,7 +336,7 @@ namespace Barotrauma
UpdateTutorialList();
campaignSetupUI.UpdateSubList();
campaignSetupUI.UpdateSubList(Submarine.SavedSubmarines);
ResetButtonStates(null);

View File

@@ -632,6 +632,13 @@ namespace Barotrauma
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),
TextManager.Get("StartGameButton"), style: "GUIButtonLarge")
{
@@ -644,13 +651,6 @@ namespace Barotrauma
};
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),
TextManager.Get("SpectateButton"), style: "GUIButtonLarge");
}
@@ -726,6 +726,12 @@ namespace Barotrauma
spectateButton.Visible = GameMain.Client.GameStarted;
ReadyToStartBox.Visible = !GameMain.Client.GameStarted;
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);
}
else
@@ -844,6 +850,13 @@ namespace Barotrauma
GameMain.Client.ShowLogButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ServerLog);
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()
@@ -1191,7 +1204,7 @@ namespace Barotrauma
if (sub.HasTag(SubmarineTag.Shuttle))
{
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight),
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight) { RelativeOffset = new Vector2(0.1f, 0.0f) },
TextManager.Get("Shuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{
TextColor = subTextBlock.TextColor * 0.8f,

View File

@@ -716,7 +716,7 @@ namespace Barotrauma
catch (PingException ex)
{
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.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg);
#if DEBUG
DebugConsole.NewMessage(errorMsg, Color.Red);
#endif

View File

@@ -26,6 +26,10 @@ namespace Barotrauma
private int subDivX, subDivY;
private static Effect effect;
public static Effect Effect
{
get { return effect; }
}
private Point spritePos;
private Point spriteSize;

View File

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.8.9.6")]
[assembly: AssemblyFileVersion("0.8.9.6")]
[assembly: AssemblyVersion("0.8.9.7")]
[assembly: AssemblyFileVersion("0.8.9.7")]

View File

@@ -4,11 +4,6 @@ namespace Barotrauma
{
partial class CharacterInfo
{
partial void SpawnInventoryItemProjSpecific(Item item)
{
Entity.Spawner.CreateNetworkEvent(item, false);
}
public void ServerWrite(NetBuffer msg)
{
msg.Write(ID);

View File

@@ -81,11 +81,12 @@ namespace Barotrauma
//dequeue messages
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.Write(new string(' ', consoleWidth));
Console.CursorTop--; Console.CursorLeft = 0;
Console.CursorTop -= inputLines; Console.CursorLeft = 0;
while (queuedMessages.Count > 0)
{
ColoredText msg = queuedMessages.Dequeue();
@@ -177,11 +178,11 @@ namespace Barotrauma
private static void RewriteInputToCommandLine(string input)
{
Console.WriteLine(""); Console.CursorTop--;
int consoleWidth = Console.WindowWidth;
if (consoleWidth < 5) consoleWidth = 5;
int consoleHeight = Console.WindowHeight;
if (consoleHeight < 5) consoleHeight = 5;
int consoleWidth = Math.Max(Console.WindowWidth, 5);
int inputLines = Math.Max((int)Math.Ceiling(input.Length / (float)consoleWidth), 1);
int cursorLine = Math.Max((int)Math.Ceiling((input.Length + 1) / (float)consoleWidth), 1);
Console.WriteLine(""); Console.CursorTop -= inputLines;
string ln = input.Length > 0 ? AutoComplete(input, 0) : "";
ln += new string(' ', consoleWidth - (ln.Length % consoleWidth));
@@ -190,9 +191,9 @@ namespace Barotrauma
Console.Write(ln);
Console.ForegroundColor = ConsoleColor.White;
Console.CursorLeft = 0;
Console.CursorTop--;
Console.CursorTop -= cursorLine;
Console.Write(input);
Console.CursorLeft = input.Length;
Console.CursorLeft = input.Length % Console.WindowWidth;
}
private static void AssignOnClientRequestExecute(string names, Action<Client, Vector2, string[]> onClientRequestExecute)
@@ -1554,9 +1555,9 @@ namespace Barotrauma
}));
#if DEBUG
commands.Add(new Command("spamevents", "A debug command that immediately creates entity events for all items, characters and structures.", (string[] args) =>
commands.Add(new Command("spamevents", "A debug command that creates a ton of entity events.", (string[] args) =>
{
foreach (Item item in Item.ItemList)
/*foreach (Item item in Item.ItemList)
{
foreach (ItemComponent component in item.Components)
{
@@ -1573,12 +1574,14 @@ namespace Barotrauma
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.Status });
}
}
foreach (Character c in Character.CharacterList)
{
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)
{
GameMain.Server.CreateEntityEvent(wall);

View File

@@ -11,11 +11,11 @@ namespace Barotrauma
{
private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
public static void StartNewCampaign(string savePath, string subName, string seed)
public static void StartNewCampaign(string savePath, string subPath, string seed)
{
if (string.IsNullOrWhiteSpace(savePath)) return;
GameMain.GameSession = new GameSession(new Submarine(subName, ""), savePath,
GameMain.GameSession = new GameSession(new Submarine(subPath, ""), savePath,
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign"));
var campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
campaign.GenerateMap(seed);
@@ -72,6 +72,27 @@ 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)
{
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
@@ -85,8 +106,7 @@ namespace Barotrauma
if (GameMain.Server != null)
{
var client = GameMain.Server.ConnectedClients.Find(c => c.Character == interactor);
hasPermissions = client != null &&
(client.HasPermission(ClientPermissions.ManageRound) || client.HasPermission(ClientPermissions.ManageCampaign));
hasPermissions = client != null;
CreateDialog(new List<Character> { watchman }, hasPermissions ? "WatchmanInteract" : "WatchmanInteractNotAllowed", 1.0f);
}
}
@@ -231,6 +251,7 @@ namespace Barotrauma
}
lastSaveID++;
DebugConsole.Log("Campaign saved, save ID " + lastSaveID);
}
}
}

View File

@@ -1,9 +1,5 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
namespace Barotrauma.Items.Components
{
@@ -14,5 +10,16 @@ namespace Barotrauma.Items.Components
//let the clients know the initial deterioration delay
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);
}
}
}

View File

@@ -335,6 +335,14 @@ namespace Barotrauma
{
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);
if (index == -1) return;

View File

@@ -20,6 +20,16 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
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%
//or if oxygen percentage has changed by 5%
if (Math.Abs(lastSentVolume - waterVolume) > Volume * 0.1f ||
@@ -31,7 +41,7 @@ namespace Barotrauma
GameMain.NetworkMember.CreateEntityEvent(this);
lastSentVolume = waterVolume;
lastSentOxygen = OxygenPercentage;
sendUpdateTimer = NetworkUpdateInterval;
sendUpdateTimer = NetConfig.HullUpdateInterval;
}
}
}

View File

@@ -46,7 +46,7 @@ namespace Barotrauma.Networking
//when was a specific entity event last sent to the client
// key = event id, value = NetTime.Now when sending
public readonly Dictionary<UInt16, float> EntityEventLastSent = new Dictionary<UInt16, float>();
public readonly Dictionary<UInt16, double> EntityEventLastSent = new Dictionary<UInt16, double>();
//when was a position update for a given entity last sent to the client
// key = entity id, value = NetTime.Now when sending

View File

@@ -187,7 +187,7 @@ namespace Barotrauma.Networking
transfer.WaitTimer -= deltaTime;
if (transfer.WaitTimer > 0.0f) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, 1)) continue;
if (!transfer.Connection.CanSendImmediately(NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel)) continue;
transfer.WaitTimer = 0.05f;// transfer.Connection.AverageRoundtripTime;
@@ -202,15 +202,6 @@ namespace Barotrauma.Networking
{
message = peer.CreateMessage();
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)
//we don't need to send anything, the client can just read the file directly

View File

@@ -43,6 +43,7 @@ namespace Barotrauma.Networking
private IRestResponse masterServerResponse;
private bool autoRestartTimerRunning;
private float endRoundTimer;
public VoipServer VoipServer
{
@@ -405,20 +406,38 @@ 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
if ((serverSettings.AutoRestart && isCrewDead)
||
(serverSettings.EndRoundAtLevelEnd && subAtLevelEnd))
(serverSettings.EndRoundAtLevelEnd && subAtLevelEnd)
||
(isCrewDead && respawnManager == null && endRoundTimer >= 60.0f))
{
if (serverSettings.AutoRestart && isCrewDead)
{
Log("Ending round (entire crew dead)", ServerLog.MessageType.ServerMessage);
}
else
else if (serverSettings.EndRoundAtLevelEnd && subAtLevelEnd)
{
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();
return;
}
@@ -697,13 +716,24 @@ namespace Barotrauma.Networking
string savePath = inc.ReadString();
string seed = inc.ReadString();
string subName = inc.ReadString();
string subHash = inc.ReadString();
if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.StartNewCampaign(savePath, subName, seed);
}
var matchingSub = Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash);
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
{
string saveName = inc.ReadString();
if (connectedClient.HasPermission(ClientPermissions.SelectMode)) MultiPlayerCampaign.LoadCampaign(saveName);
}
break;
@@ -734,6 +764,64 @@ namespace Barotrauma.Networking
fileSender.ReadFileRequest(inc, connectedClient);
}
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);
}
}
@@ -791,8 +879,8 @@ namespace Barotrauma.Networking
//(the server started a new campaign and the client isn't aware of it yet?)
if (campaign.CampaignID != campaignID)
{
c.LastRecvCampaignSave = 0;
c.LastRecvCampaignUpdate = 0;
c.LastRecvCampaignSave = (ushort)(campaign.LastSaveID - 1);
c.LastRecvCampaignUpdate = (ushort)(campaign.LastUpdateID - 1);
}
}
}
@@ -946,7 +1034,17 @@ namespace Barotrauma.Networking
return;
}
if (!sender.HasPermission(command))
//clients are allowed to end the round by talking with the watchman in multiplayer
//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))
{
return;
}
}
else if (!sender.HasPermission(command))
{
Log("Client \"" + sender.Name + "\" sent a server command \"" + command + "\". Permission denied.", ServerLog.MessageType.ServerMessage);
return;
@@ -1248,10 +1346,6 @@ namespace Barotrauma.Networking
WriteClientList(c, outmsg);
clientListBytes = outmsg.LengthBytes - clientListBytes;
int eventManagerBytes = outmsg.LengthBytes;
entityEventManager.Write(c, outmsg, out List<NetEntityEvent> sentEvents);
eventManagerBytes = outmsg.LengthBytes - eventManagerBytes;
int chatMessageBytes = outmsg.LengthBytes;
WriteChatMessages(outmsg, c);
chatMessageBytes = outmsg.LengthBytes - chatMessageBytes;
@@ -1300,25 +1394,55 @@ namespace Barotrauma.Networking
errorMsg +=
" Client list size: " + clientListBytes + " bytes\n" +
" Chat message size: " + chatMessageBytes + " bytes\n" +
" Event size: " + eventManagerBytes + " bytes\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);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.ClientWriteIngame1:PacketSizeExceeded" + outmsg.LengthBytes, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
CompressOutgoingMessage(outmsg);
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)
@@ -1562,9 +1686,23 @@ namespace Barotrauma.Networking
Rand.SetSyncedSeed(roundStartSeed);
int teamCount = 1;
MultiPlayerCampaign campaign = GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
MultiPlayerCampaign campaign = selectedMode == GameMain.GameSession?.GameMode.Preset ?
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
if (campaign == null || GameMain.GameSession == null)
{
@@ -1589,16 +1727,18 @@ namespace Barotrauma.Networking
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
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
{
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 &&
(!(GameMain.GameSession.GameMode is MissionMode) ||
((MissionMode)GameMain.GameSession.GameMode).Mission.AllowRespawn);
@@ -1759,6 +1899,7 @@ namespace Barotrauma.Networking
msg.Write(seed);
msg.Write(GameMain.GameSession.Level.Seed);
msg.Write(GameMain.GameSession.Level.EqualityCheckVal);
msg.Write(serverSettings.SelectedLevelDifficulty);
msg.Write((byte)GameMain.Config.LosMode);
@@ -1821,6 +1962,8 @@ namespace Barotrauma.Networking
Mission mission = GameMain.GameSession.Mission;
GameMain.GameSession.GameMode.End(endMessage);
endRoundTimer = 0.0f;
if (serverSettings.AutoRestart)
{
serverSettings.AutoRestartTimer = serverSettings.AutoRestartInterval;
@@ -1897,14 +2040,7 @@ namespace Barotrauma.Networking
public override void AddChatMessage(ChatMessage message)
{
if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null)
{
Log($"{message.Sender}: {message.Text}", ServerLog.MessageType.Chat);
}
else
{
Log($"{message.Text}", ServerLog.MessageType.Chat);
}
Log(message.TextWithSender, ServerLog.MessageType.Chat);
base.AddChatMessage(message);
}
@@ -2367,6 +2503,8 @@ namespace Barotrauma.Networking
public void SendVoteStatus(List<Client> recipients)
{
if (!recipients.Any()) { return; }
NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ServerNetObject.VOTE);
@@ -2419,7 +2557,10 @@ namespace Barotrauma.Networking
recipients.Add(otherClient.Connection);
}
}
server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
if (recipients.Any())
{
server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
}
serverSettings.SaveClientPermissions();
}
@@ -2453,6 +2594,8 @@ namespace Barotrauma.Networking
public void UpdateCheatsEnabled()
{
if (!connectedClients.Any()) { return; }
var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED);
msg.Write(DebugConsole.CheatsEnabled);

View File

@@ -158,15 +158,15 @@ namespace Barotrauma.Networking
}
//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);
if (connectedClient != null)
{
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)
@@ -342,7 +342,7 @@ namespace Barotrauma.Networking
if (clVersion != GameMain.Version.ToString())
{
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);
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)
{
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);
return;
}
@@ -376,7 +376,7 @@ namespace Barotrauma.Networking
{
List<string> packageStrs = new List<string>();
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);
return;
}
@@ -399,7 +399,7 @@ namespace Barotrauma.Networking
if (incompatiblePackages.Count == 1)
{
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);
return;
}
@@ -408,7 +408,7 @@ namespace Barotrauma.Networking
List<string> packageStrs = new List<string>();
incompatiblePackages.ForEach(cp => packageStrs.Add(GetPackageStr2(cp)));
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);
return;
}
@@ -500,7 +500,7 @@ namespace Barotrauma.Networking
private void DisconnectUnauthClient(NetIncomingMessage inc, UnauthenticatedClient unauthClient, DisconnectReason reason, string message)
{
inc.SenderConnection.Disconnect(reason.ToString() + "/ " + message);
inc.SenderConnection.Disconnect(reason.ToString() + "/ " + TextManager.GetServerMessage(message));
if (unauthClient.SteamID > 0) { Steam.SteamManager.StopAuthSession(unauthClient.SteamID); }
if (unauthClient != null)
{

View File

@@ -1,4 +1,5 @@
using Lidgren.Network;
using Barotrauma.Extensions;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
@@ -208,7 +209,6 @@ namespace Barotrauma.Networking
lastSentToAll = owner.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); });
@@ -228,8 +228,8 @@ namespace Barotrauma.Networking
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected old event "
+ (c.LastRecvEntityEventID + 1).ToString() +
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" +
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.ServerMessage);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesync");
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.Error);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncOldEvent");
}
);
}
@@ -242,8 +242,8 @@ namespace Barotrauma.Networking
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);
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.ExcessiveDesync");
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);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncRemovedEvent");
});
}
}
@@ -251,7 +251,7 @@ namespace Barotrauma.Networking
var timedOutClients = clients.FindAll(c => c.InGame && c.NeedsMidRoundSync && Timing.TotalTime > c.MidRoundSyncTimeOut);
foreach (Client timedOutClient in timedOutClients)
{
GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.ServerMessage);
GameServer.Log("Disconnecting client " + timedOutClient.Name + ". Syncing the client with the server took too long.", ServerLog.MessageType.Error);
GameMain.Server.DisconnectClient(timedOutClient, "", "ServerMessage.SyncTimeout");
}
@@ -305,19 +305,7 @@ namespace Barotrauma.Networking
}
//too many events for one packet
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)
{
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync)
{
@@ -347,24 +335,22 @@ namespace Barotrauma.Networking
if (client.NeedsMidRoundSync)
{
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.FirstNewEventID);
Write(msg, eventsToSync, client);
Write(msg, eventsToSync, out sentEvents, client);
}
else
{
msg.Write((byte)ServerNetObject.ENTITY_EVENT);
Write(msg, eventsToSync, out sentEvents, client);
}
foreach (NetEntityEvent entityEvent in sentEvents)
{
(entityEvent as ServerEntityEvent).Sent = true;
client.EntityEventLastSent[entityEvent.ID] = NetTime.Now;
}
sentEvents = eventsToSync;
}
@@ -389,10 +375,10 @@ namespace Barotrauma.Networking
for (int i = startIndex; i < eventList.Count; i++)
{
//find the first event that hasn't been sent in 1.5 * roundtriptime or at all
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out float lastSent);
//find the first event that hasn't been sent in roundtriptime or at all
client.EntityEventLastSent.TryGetValue(eventList[i].ID, out double lastSent);
float minInterval = Math.Max(client.Connection.AverageRoundtripTime * 1.5f, (float)server.UpdateInterval.TotalSeconds * 2);
float minInterval = Math.Max(client.Connection.AverageRoundtripTime, (float)server.UpdateInterval.TotalSeconds * 2);
if (lastSent > NetTime.Now - Math.Min(minInterval, 0.5f))
{
@@ -418,7 +404,7 @@ namespace Barotrauma.Networking
}
else
{
double midRoundSyncTimeOut = uniqueEvents.Count / MaxEventsPerWrite * server.UpdateInterval.TotalSeconds;
double midRoundSyncTimeOut = uniqueEvents.Count / 100 * server.UpdateInterval.TotalSeconds;
midRoundSyncTimeOut = Math.Max(10.0f, midRoundSyncTimeOut * 10.0f);
client.UnreceivedEntityEventCount = (UInt16)uniqueEvents.Count;

View File

@@ -78,6 +78,7 @@ namespace Barotrauma.Networking
bool senderSpectating = sender.Character == null || sender.Character.IsDead;
//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; }
//both spectating, no need to do radio/distance checks

View File

@@ -116,8 +116,8 @@ namespace Barotrauma
msg.Write(AllowEndVoting);
if (AllowEndVoting)
{
msg.Write((byte)GameMain.Server.ConnectedClients.Count(v => v.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count);
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound)));
msg.Write((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned));
}
msg.Write(AllowVoteKick);

View File

@@ -73,6 +73,9 @@
<Submarine file="Submarines/Muikku.sub" />
<Submarine file="Submarines/Bunyip.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" />
<UIStyle file="Content/UI/style.xml"/>
<Afflictions file="Content/Afflictions.xml"/>

View File

@@ -1670,6 +1670,12 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\lightcone.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</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">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -3150,8 +3156,10 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Typhon.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@@ -110,7 +110,7 @@ namespace Barotrauma
SonarLabel = element.GetAttributeString("sonarlabel", "");
}
public AITarget(Entity e)
public AITarget(Entity e, float sightRange = 3000, float soundRange = 0)
{
Entity = e;
SightRange = sightRange;

View File

@@ -26,7 +26,6 @@ namespace Barotrauma
}
}
public class TargetingPriority
{
public string TargetTag;
@@ -100,6 +99,9 @@ namespace Barotrauma
private readonly float aggressiongreed;
private readonly float aggressionhurt;
// TODO: expose?
private readonly float priorityFearIncreasement = 2;
private readonly float memoryFadeTime = 0.5f;
public bool AttackHumans
{
@@ -249,8 +251,7 @@ namespace Barotrauma
public override void SelectTarget(AITarget target)
{
SelectedAiTarget = target;
selectedTargetMemory = FindTargetMemory(target);
selectedTargetMemory = GetTargetMemory(target);
targetValue = 100.0f;
}
@@ -287,8 +288,7 @@ namespace Barotrauma
}
else
{
TargetingPriority targetingPriority = null;
UpdateTargets(Character, out targetingPriority);
var targetingPriority = UpdateTargets(Character);
updateTargetsTimer = UpdateTargetsInterval;
if (SelectedAiTarget == null)
@@ -395,13 +395,44 @@ namespace Barotrauma
State = AIState.Idle;
return;
}
Vector2 escapeDir = Vector2.Normalize(SimPosition - SelectedAiTarget.SimPosition);
if (!MathUtils.IsValid(escapeDir)) escapeDir = Vector2.UnitY;
SteeringManager.SteeringManual(deltaTime, escapeDir);
SteeringManager.SteeringWander();
if (Character.CurrentHull == null)
else if (selectedTargetMemory != 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);
}
}
@@ -418,14 +449,8 @@ namespace Barotrauma
return;
}
selectedTargetMemory.Priority -= deltaTime * 0.1f;
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);
}
Vector2 attackWorldPos = SelectedAiTarget.WorldPosition;
Vector2 attackSimPos = SelectedAiTarget.SimPosition;
if (SelectedAiTarget.Entity is Item item)
{
@@ -441,22 +466,24 @@ namespace Barotrauma
}
}
if (wallTarget != null)
if (raycastTimer > 0.0)
{
attackSimPosition = ConvertUnits.ToSimUnits(wallTarget.Position);
if (Character.Submarine == null && SelectedAiTarget.Entity?.Submarine != null)
{
attackSimPosition += ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
}
raycastTimer -= deltaTime;
}
else if (SelectedAiTarget.Entity is Character c)
else
{
UpdateWallTarget();
raycastTimer = RaycastInterval;
}
if (SelectedAiTarget.Entity is Character c)
{
//target the closest limb if the target is a character
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.SimPosition, SimPosition) * 10.0f;
foreach (Limb limb in ((Character)SelectedAiTarget.Entity).AnimController.Limbs)
float closestDist = Vector2.DistanceSquared(SelectedAiTarget.WorldPosition, WorldPosition) * 10.0f;
foreach (Limb limb in c.AnimController.Limbs)
{
if (limb == null) continue;
float dist = Vector2.DistanceSquared(limb.SimPosition, SimPosition) / Math.Max(limb.AttackPriority, 0.1f);
float dist = Vector2.DistanceSquared(limb.WorldPosition, WorldPosition) / Math.Max(limb.AttackPriority, 0.1f);
if (dist < closestDist)
{
closestDist = dist;
@@ -466,12 +493,30 @@ namespace Barotrauma
}
}
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
if (wallTarget != null)
{
Character.AnimController.TargetDir = Character.SimPosition.X < attackSimPosition.X ? Direction.Right : Direction.Left;
attackWorldPos = wallTarget.Position;
attackSimPos = ConvertUnits.ToSimUnits(attackWorldPos);
}
else
{
// Take the sub position into account in the sim pos
if (Character.Submarine == null && SelectedAiTarget.Entity.Submarine != null)
{
attackSimPos += SelectedAiTarget.Entity.Submarine.SimPosition;
}
else if (Character.Submarine != null && SelectedAiTarget.Entity.Submarine == null)
{
attackSimPos -= Character.Submarine.SimPosition;
}
}
if (raycastTimer > 0.0)
if (Math.Abs(Character.AnimController.movement.X) > 0.1f && !Character.AnimController.InWater)
{
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
if (wallTarget != null && wallTarget.SectionIndex > -1 && CanPassThroughHole(wallTarget.Structure, wallTarget.SectionIndex))
@@ -529,72 +574,6 @@ namespace Barotrauma
}
}
}
else
{
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;
if (IsCoolDownRunning)
@@ -612,7 +591,7 @@ namespace Barotrauma
}
else
{
UpdateFallBack(attackSimPosition, deltaTime);
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
}
@@ -620,14 +599,14 @@ namespace Barotrauma
{
if (attackingLimb.attack.SecondaryCoolDownTimer <= 0)
{
// Don't allow attacking when the attack target has changed.
// Don't allow attacking when the attack target has just changed.
if (_previousAiTarget != null && SelectedAiTarget != _previousAiTarget)
{
canAttack = false;
if (attackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.PursueIfCanAttack)
{
// Fall back if cannot attack.
UpdateFallBack(attackSimPosition, deltaTime);
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
attackingLimb = null;
@@ -636,7 +615,7 @@ namespace Barotrauma
{
// If the secondary cooldown is defined and expired, check if we can switch the attack
var previousLimb = attackingLimb;
var newLimb = GetAttackLimb(attackSimPosition, previousLimb);
var newLimb = GetAttackLimb(attackWorldPos, previousLimb);
if (newLimb != null)
{
attackingLimb = newLimb;
@@ -650,7 +629,7 @@ namespace Barotrauma
}
else
{
UpdateFallBack(attackSimPosition, deltaTime);
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
}
@@ -665,15 +644,15 @@ namespace Barotrauma
break;
case AIBehaviorAfterAttack.FallBack:
default:
UpdateFallBack(attackSimPosition, deltaTime);
UpdateFallBack(attackWorldPos, deltaTime);
return;
}
}
if (attackingLimb == null)
if (attackingLimb == null || _previousAiTarget != SelectedAiTarget)
{
attackingLimb = GetAttackLimb(attackSimPosition);
attackingLimb = GetAttackLimb(attackWorldPos);
}
if (canAttack)
{
@@ -683,24 +662,39 @@ namespace Barotrauma
if (canAttack)
{
// Check that we can reach the target
distance = ConvertUnits.ToDisplayUnits(Vector2.Distance(attackingLimb.SimPosition, attackSimPosition));
distance = Vector2.Distance(attackingLimb.WorldPosition, attackWorldPos);
canAttack = distance < attackingLimb.attack.Range;
}
Limb steeringLimb = Character.AnimController.MainLimb;
// 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.
// 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)
{
Vector2 steeringVector = attackSimPosition - steeringLimb.SimPosition;
Vector2 targetingVector = Vector2.Normalize(steeringVector) * attackingLimb.attack.Range;
// Offset the position a bit so that we don't overshoot the movement.
Vector2 steerPos = attackSimPosition + targetingVector;
steeringManager.SteeringSeek(steerPos, 10);
if (Character.CurrentHull == null)
{
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
}
Vector2 offset = Character.SimPosition - steeringLimb.SimPosition;
// Offset so that we don't overshoot the movement
Vector2 steerPos = attackSimPos + offset;
SteeringManager.SteeringSeek(steerPos, 10);
if (steeringManager is IndoorsSteeringManager indoorsSteering)
if (SteeringManager is IndoorsSteeringManager indoorsSteering)
{
if (indoorsSteering.CurrentPath != null && !indoorsSteering.IsPathDirty)
{
@@ -715,7 +709,7 @@ namespace Barotrauma
}
else if (indoorsSteering.CurrentPath.Finished)
{
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(steeringVector));
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else if (indoorsSteering.CurrentPath.CurrentNode?.ConnectedDoor != null)
{
@@ -729,40 +723,42 @@ namespace Barotrauma
}
}
}
else if (Character.CurrentHull == null)
{
SteeringManager.SteeringAvoid(deltaTime, colliderSize * 1.5f);
}
}
if (canAttack)
{
UpdateLimbAttack(deltaTime, attackingLimb, attackSimPosition, distance);
UpdateLimbAttack(deltaTime, attackingLimb, attackSimPos, distance);
}
}
private Limb GetAttackLimb(Vector2 attackSimPosition, Limb ignoredLimb = null)
private bool SteerThroughGap(Structure wall, WallSection section, Vector2 targetWorldPos, float deltaTime)
{
AttackContext currentContext = Character.GetAttackContext();
var target = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity;
var limbs = Character.AnimController.Limbs
.Where(l =>
l != ignoredLimb &&
l.attack != null &&
!l.IsSevered &&
!l.IsStuck &&
l.attack.IsValidContext(currentContext) &&
l.attack.IsValidTarget(target) &&
l.attack.Conditionals.All(c => (target is ISerializableEntity se && c.Matches(se)) || !(target is ISerializableEntity) || !(target is Character)))
.OrderByDescending(l => l.attack.Priority)
.ThenBy(l => ConvertUnits.ToDisplayUnits(Vector2.Distance(l.SimPosition, attackSimPosition)));
// TODO: priority should probably not override the distance -> use values instead of booleans
return limbs.FirstOrDefault();
}
private void UpdateWallTarget()
{
wallTarget = null;
if (Character.AnimController.CurrentHull != null)
Hull targetHull = section.gap?.FlowTargetHull;
if (targetHull != null)
{
return;
if (wall.IsHorizontal)
{
targetWorldPos.Y = targetHull.WorldRect.Y - targetHull.Rect.Height / 2;
}
else
{
targetWorldPos.X = targetHull.WorldRect.Center.X;
}
latchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
if (steeringManager is IndoorsSteeringManager)
{
steeringManager.SteeringManual(deltaTime, Vector2.Normalize(targetWorldPos - Character.WorldPosition));
}
else
{
steeringManager.SteeringSeek(ConvertUnits.ToSimUnits(targetWorldPos));
}
return true;
}
return false;
}
@@ -791,32 +787,23 @@ namespace Barotrauma
wallTarget = null;
//check if there's a wall between the target and the Character
Vector2 rayStart = Character.SimPosition;
Vector2 rayStart = SimPosition;
Vector2 rayEnd = SelectedAiTarget.SimPosition;
bool offset = SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null;
if (SelectedAiTarget.Entity.Submarine != null && Character.Submarine == null)
if (offset)
{
rayStart -= ConvertUnits.ToSimUnits(SelectedAiTarget.Entity.Submarine.Position);
rayStart -= SelectedAiTarget.Entity.Submarine.SimPosition;
}
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd);
Body closestBody = Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true);
if (Submarine.LastPickedFraction == 1.0f || closestBody == null)
{
return;
}
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
if (closestBody.UserData is Structure wall && wall.Submarine != null)
{
int sectionIndex = wall.FindSectionIndex(ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition));
int passableHoleCount = GetMinimumPassableHoleCount();
@@ -826,33 +813,40 @@ namespace Barotrauma
{
if (wall.SectionBodyDisabled(i))
{
if (aggressiveBoarding && CanPassThroughHole(wall, i)) //aggressive boarders always target holes they can pass through
if (aggressiveBoarding && CanPassThroughHole(wall, i))
{
//aggressive boarders always target holes they can pass through
sectionIndex = i;
break;
}
else //otherwise ignore and keep breaking other sections
else
{
//otherwise ignore and keep breaking other sections
continue;
}
}
if (wall.SectionDamage(i) > sectionDamage) sectionIndex = i;
}
Vector2 sectionPos = ConvertUnits.ToSimUnits(wall.SectionPosition(sectionIndex));
Vector2 sectionPos = wall.SectionPosition(sectionIndex);
Vector2 attachTargetNormal;
if (wall.IsHorizontal)
{
attachTargetNormal = new Vector2(0.0f, Math.Sign(Character.WorldPosition.Y - wall.WorldPosition.Y));
sectionPos.Y += ConvertUnits.ToSimUnits((wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2) * attachTargetNormal.Y;
attachTargetNormal = new Vector2(0.0f, Math.Sign(WorldPosition.Y - wall.WorldPosition.Y));
sectionPos.Y += (wall.BodyHeight <= 0.0f ? wall.Rect.Height : wall.BodyHeight) / 2 * attachTargetNormal.Y;
}
else
{
attachTargetNormal = new Vector2(Math.Sign(Character.WorldPosition.X - wall.WorldPosition.X), 0.0f);
sectionPos.X += ConvertUnits.ToSimUnits((wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2) * attachTargetNormal.X;
attachTargetNormal = new Vector2(Math.Sign(WorldPosition.X - wall.WorldPosition.X), 0.0f);
sectionPos.X += (wall.BodyWidth <= 0.0f ? wall.Rect.Width : wall.BodyWidth) / 2 * attachTargetNormal.X;
}
wallTarget = new WallTarget(ConvertUnits.ToDisplayUnits(sectionPos), wall, sectionIndex);
latchOntoAI?.SetAttachTarget(wall.Submarine.PhysicsBody.FarseerBody, wall.Submarine, sectionPos, attachTargetNormal);
if (wall.Submarine != null)
{
sectionPos += wall.Submarine.Position;
}
wallTarget = new WallTarget(sectionPos, wall, sectionIndex);
}
}
@@ -860,16 +854,6 @@ namespace Barotrauma
{
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 (!(attacker is AICharacter) || (((AICharacter)attacker).AIController is HumanAIController))
@@ -883,37 +867,60 @@ namespace Barotrauma
Character.AnimController.ReleaseStuckLimbs();
if (attacker == null || attacker.AiTarget == null) return;
AITargetMemory targetMemory = FindTargetMemory(attacker.AiTarget);
AITargetMemory targetMemory = GetTargetMemory(attacker.AiTarget);
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
private float GetRelativeDamage(float dmg, float vitality) => dmg / Math.Max(vitality, 1.0f);
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackPosition, float distance = -1)
private void UpdateLimbAttack(float deltaTime, Limb limb, Vector2 attackSimPos, float distance = -1)
{
var damageTarget = wallTarget != null ? wallTarget.Structure : SelectedAiTarget.Entity as IDamageable;
if (damageTarget == null) return;
float prevHealth = damageTarget.Health;
if (limb.UpdateAttack(deltaTime, attackPosition, damageTarget, out AttackResult attackResult, distance))
if (SelectedAiTarget == null) { return; }
if (wallTarget != null)
{
if (damageTarget.Health > 0)
// If the selected target is not the wall target, make the wall target the selected target.
var aiTarget = wallTarget.Structure.AiTarget;
if (aiTarget != null && SelectedAiTarget != 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;
SelectTarget(aiTarget);
}
}
if (!limb.attack.IsRunning)
if (SelectedAiTarget.Entity is IDamageable damageTarget)
{
wallTarget = null;
float prevHealth = damageTarget.Health;
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 attackPosition, float deltaTime)
private void UpdateFallBack(Vector2 attackWorldPos, float deltaTime)
{
float dist = Vector2.Distance(attackPosition, Character.SimPosition);
Vector2 attackVector = attackWorldPos - WorldPosition;
float dist = attackVector.Length();
float desiredDist = colliderSize * 2.0f;
if (dist < desiredDist)
{
@@ -921,7 +928,6 @@ namespace Barotrauma
if (!MathUtils.IsValid(attackDir)) attackDir = Vector2.UnitY;
steeringManager.SteeringManual(deltaTime, attackDir * (1.0f - (dist / 500.0f)));
}
steeringManager.SteeringAvoid(deltaTime, colliderSize * 3.0f);
}
@@ -972,15 +978,13 @@ namespace Barotrauma
//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
//sight/hearing range
public void UpdateTargets(Character character, out TargetingPriority targetingPriority)
public TargetingPriority UpdateTargets(Character character)
{
targetingPriority = null;
SelectedAiTarget = null;
AITarget newTarget = null;
TargetingPriority targetingPriority = null;
selectedTargetMemory = null;
targetValue = 0.0f;
UpdateTargetMemories();
foreach (AITarget target in AITarget.List)
{
if (!target.Enabled) continue;
@@ -989,48 +993,67 @@ namespace Barotrauma
continue;
}
float valueModifier = 1.0f;
float dist = 0.0f;
Character targetCharacter = target.Entity as Character;
//ignore the aitarget if it is the Character itself
if (targetCharacter == character) continue;
float valueModifier = 1;
string targetingTag = null;
if (targetCharacter != null)
{
if (targetCharacter.Submarine != null && Character.Submarine == null)
{
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)
{
if (enemy.combatStrength > combatStrength)
{
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)
{
//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";
}
else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
{
targetingTag = targetCharacter.SpeciesName.ToLowerInvariant();
}
else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
{
targetingTag = targetCharacter.SpeciesName.ToLowerInvariant();
}
else if (targetingPriorities.ContainsKey(targetCharacter.SpeciesName.ToLowerInvariant()))
{
if (targetCharacter.AIController is EnemyAIController enemy)
{
if (enemy.combatStrength > combatStrength)
{
targetingTag = "stronger";
}
else if (enemy.combatStrength < combatStrength)
{
targetingTag = "weaker";
}
}
}
}
else if (target.Entity != null)
{
@@ -1056,19 +1079,63 @@ 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
{
targetingTag = "room";
}
if (door != null)
{
//increase priority if the character is outside and an aggressive boarder, and the door is from outside to inside
if (character.CurrentHull == null && aggressiveBoarding && !door.LinkedGap.IsRoomToRoom)
// If there's not a more specific tag for the door
if (string.IsNullOrEmpty(targetingTag) || targetingTag == "room")
{
valueModifier = door.IsOpen ? 10 : 5;
targetingTag = "door";
}
else if (door.IsOpen || door.Item.Condition <= 0.0f) //ignore broken and open doors
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
if (aggressiveBoarding)
{
if (character.CurrentHull == null)
{
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
{
continue;
}
@@ -1084,10 +1151,15 @@ namespace Barotrauma
valueModifier *= targetingPriorities[targetingTag].Priority;
if (targetingTag == null) continue;
if (!targetingPriorities.ContainsKey(targetingTag)) continue;
valueModifier *= targetingPriorities[targetingTag].Priority;
if (valueModifier == 0.0f) continue;
Vector2 toTarget = target.WorldPosition - character.WorldPosition;
dist = toTarget.Length();
float dist = toTarget.Length();
//if the target has been within range earlier, the character will notice it more easily
//(i.e. remember where the target was)
@@ -1101,28 +1173,29 @@ namespace Barotrauma
// -> just ignore the distance and attack whatever has the highest priority
dist = Math.Max(dist, 100.0f);
AITargetMemory targetMemory = FindTargetMemory(target);
AITargetMemory targetMemory = GetTargetMemory(target);
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.
dist *= 3;
}
valueModifier = valueModifier * targetMemory.Priority / (float)Math.Sqrt(dist);
valueModifier *= targetMemory.Priority / (float)Math.Sqrt(dist);
if (valueModifier > targetValue)
{
SelectedAiTarget = target;
newTarget = target;
selectedTargetMemory = targetMemory;
targetingPriority = targetingPriorities[targetingTag];
targetValue = valueModifier;
}
}
SelectedAiTarget = newTarget;
if (SelectedAiTarget != _previousAiTarget)
{
wallTarget = null;
}
_previousAiTarget = SelectedAiTarget;
return targetingPriority;
}
private AITargetMemory GetTargetMemory(AITarget target)
@@ -1132,69 +1205,39 @@ namespace Barotrauma
memory = new AITargetMemory(10);
targetMemories.Add(target, memory);
}
memory = new AITargetMemory(10.0f);
targetMemories.Add(target, memory);
return memory;
}
private List<AITarget> removals = new List<AITarget>();
private void UpdateTargetMemories(float deltaTime)
{
List<AITarget> toBeRemoved = null;
foreach (KeyValuePair<AITarget, AITargetMemory> memory in targetMemories)
removals.Clear();
foreach (var memory in targetMemories)
{
memory.Value.Priority += 0.1f;
if (Math.Abs(memory.Value.Priority) < 1.0f || !AITarget.List.Contains(memory.Key))
// Slowly decrease all memories
memory.Value.Priority -= memoryFadeTime * deltaTime;
// Remove targets that have no priority or have been removed
if (memory.Value.Priority <= 1 || !AITarget.List.Contains(memory.Key))
{
if (toBeRemoved == null) toBeRemoved = new List<AITarget>();
toBeRemoved.Add(memory.Key);
removals.Add(memory.Key);
}
}
removals.ForEach(r => targetMemories.Remove(r));
}
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();
}
#endregion
protected override void OnStateChanged(AIState from, AIState to)
{
latchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
escapePoint = Vector2.Zero;
wallTarget = null;
}
private int GetMinimumPassableHoleCount()
{
return (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderSize) / Structure.WallSectionSize);
}
#endregion
protected override void OnStateChanged(AIState from, AIState to)
{
latchOntoAI?.DeattachFromBody();
Character.AnimController.ReleaseStuckLimbs();
}
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)

View File

@@ -245,7 +245,7 @@ namespace Barotrauma
foreach (Character c in Character.CharacterList)
{
if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
(c.AIController is EnemyAIController || c.TeamID != Character.TeamID))
(c.AIController is EnemyAIController || (c.TeamID != Character.TeamID && Character.TeamID != Character.TeamType.FriendlyNPC && c.TeamID != Character.TeamType.FriendlyNPC)))
{
var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
newOrder = new Order(orderPrefab, Character.CurrentHull, null);
@@ -290,7 +290,7 @@ namespace Barotrauma
public override void OnAttacked(Character attacker, AttackResult attackResult)
{
float damage = attackResult.Damage;
if (damage < 0) { return; }
if (damage <= 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed)
{
if (objectiveManager.CurrentOrder == null)
@@ -466,7 +466,9 @@ namespace Barotrauma
// 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 fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
int enemyCount = Character.CharacterList.Count(e => e.CurrentHull == hull && !e.IsDead && !e.IsUnconscious && (e.AIController is EnemyAIController || e.TeamID != character.TeamID));
int enemyCount = Character.CharacterList.Count(e =>
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)
float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;

View File

@@ -95,7 +95,7 @@ namespace Barotrauma
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
{
GetItemPriority = GetItemPriority,
IgnoreContainedItems = IgnoreAlreadyContainedItems
ignoredContainerIdentifiers = ignoredContainerIdentifiers
};
AddSubObjective(getItemObjective);
return;

View File

@@ -115,6 +115,7 @@ namespace Barotrauma
unreachable.Add(goToObjective.Target as Hull);
}
goToObjective = null;
SteeringManager.SteeringWander();
}
}
else if (currentHull != null)
@@ -131,6 +132,11 @@ namespace Barotrauma
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 &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{

View File

@@ -17,7 +17,7 @@ namespace Barotrauma
private string[] itemIdentifiers;
private Item targetItem, moveToTarget;
private int currSearchIndex;
public bool IgnoreContainedItems;
public string[] ignoredContainerIdentifiers;
private AIObjectiveGoTo goToObjective;
private float currItemPriority;
private bool equip;
@@ -99,11 +99,12 @@ namespace Barotrauma
FindTargetItem();
if (targetItem == null || moveToTarget == null)
{
SteeringManager.Reset();
SteeringManager.SteeringWander();
return;
}
if (Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
if (moveToTarget.CurrentHull == character.CurrentHull &&
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
{
int targetSlot = -1;
if (equip)
@@ -196,8 +197,12 @@ namespace Barotrauma
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
if (IgnoreContainedItems && item.Container != null) { continue; }
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
if (itemIdentifiers.None(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 (item.ParentInventory is CharacterInventory)

View File

@@ -1097,6 +1097,8 @@ namespace Barotrauma
CheckValidity();
CheckValidity();
UpdateNetPlayerPosition(deltaTime);
CheckDistFromCollider();
UpdateCollisionCategories();
@@ -1389,6 +1391,19 @@ namespace Barotrauma
}
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
DebugConsole.ThrowError(errorMsg);
#else

View File

@@ -1110,13 +1110,15 @@ namespace Barotrauma
ViewTarget = null;
if (!AllowInput) return;
Vector2 smoothedCursorDiff = cursorPosition - SmoothedCursorPosition;
if (Controlled == this)
if (Controlled == this || (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer))
{
SmoothedCursorPosition = cursorPosition;
}
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);
SmoothedCursorPosition = cursorPosition - smoothedCursorDiff;
}
@@ -1667,7 +1669,9 @@ namespace Barotrauma
}
#endif
//climb ladders automatically when pressing up/down inside their trigger area
if (SelectedConstruction == null && !AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
Ladder currentLadder = SelectedConstruction?.GetComponent<Ladder>();
if ((SelectedConstruction == null || currentLadder != null) &&
!AnimController.InWater && Screen.Selected != GameMain.SubEditorScreen)
{
bool climbInput = IsKeyDown(InputType.Up) || IsKeyDown(InputType.Down);
bool isControlled = Controlled == this;
@@ -1678,6 +1682,19 @@ namespace Barotrauma
float minDist = float.PositiveInfinity;
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)
{
minDist = dist;
@@ -1934,9 +1951,23 @@ namespace Barotrauma
//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;
if (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
IsRagdolled = IsKeyDown(InputType.Ragdoll); //Handle this here instead of Control because we can stop being ragdolled ourselves
}
//Keep us ragdolled if we were forced or we're too speedy to unragdoll
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();
UpdateSoundRange();
@@ -2518,7 +2549,14 @@ namespace Barotrauma
{
item.Submarine = inventory.Owner.Submarine;
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>())
{

View File

@@ -801,13 +801,32 @@ namespace Barotrauma
{
foreach (XElement itemElement in element.Elements())
{
var newItem = Item.Load(itemElement, inventory.Owner.Submarine);
int slotIndex = itemElement.GetAttributeInt("i", 0);
if (newItem == null) continue;
var newItem = Item.Load(itemElement, inventory.Owner.Submarine, createNetworkEvent: true);
if (newItem == null) { continue; }
SpawnInventoryItemProjSpecific(newItem);
int[] slotIndices = itemElement.GetAttributeIntArray("i", new int[] { 0 });
if (!slotIndices.Any())
{
DebugConsole.ThrowError("Invalid inventory data in character \"" + Name + "\" - no slot indices found");
continue;
}
inventory.TryPutItem(newItem, slotIndex, false, false, null);
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;
var itemContainers = newItem.GetComponents<ItemContainer>().ToList();
@@ -821,8 +840,6 @@ namespace Barotrauma
}
}
partial void SpawnInventoryItemProjSpecific(Item item);
public void ReloadHeadAttachments()
{
ResetLoadedAttachments();

View File

@@ -13,17 +13,17 @@ namespace Barotrauma
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)
{
}
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)
{
}
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)
{
Direction = dir;

View File

@@ -151,9 +151,12 @@ namespace Barotrauma
//how high the strength has to be for the affliction to take affect
public readonly float ActivationThreshold = 0.0f;
//how high the strength has to be for the affliction icon to be shown in the UI
public readonly float ShowIconThreshold = 0.0f;
public readonly float ShowIconThreshold = 0.05f;
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 DamageOverlayAlpha;
@@ -254,9 +257,11 @@ namespace Barotrauma
}
ActivationThreshold = element.GetAttributeFloat("activationthreshold", 0.0f);
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", ActivationThreshold);
ShowIconThreshold = element.GetAttributeFloat("showiconthreshold", Math.Max(ActivationThreshold, 0.05f));
MaxStrength = element.GetAttributeFloat("maxstrength", 100.0f);
ShowInHealthScannerThreshold = element.GetAttributeFloat("showinhealthscannerthreshold", Math.Max(ActivationThreshold, 0.05f));
DamageOverlayAlpha = element.GetAttributeFloat("damageoverlayalpha", 0.0f);
BurnOverlayAlpha = element.GetAttributeFloat("burnoverlayalpha", 0.0f);

View File

@@ -460,6 +460,7 @@ namespace Barotrauma
{
affliction.Strength = 0.0f;
}
CalculateVitality();
}
private void AddLimbAffliction(Limb limb, Affliction newAffliction)

View File

@@ -491,10 +491,10 @@ namespace Barotrauma
/// <summary>
/// Returns true if the attack successfully hit something. If the distance is not given, it will be calculated.
/// </summary>
public bool UpdateAttack(float deltaTime, Vector2 attackPosition, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
public bool UpdateAttack(float deltaTime, Vector2 attackSimPos, IDamageable damageTarget, out AttackResult attackResult, float distance = -1)
{
attackResult = default(AttackResult);
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackPosition));
float dist = distance > -1 ? distance : ConvertUnits.ToDisplayUnits(Vector2.Distance(SimPosition, attackSimPos));
bool wasRunning = attack.IsRunning;
attack.UpdateAttackTimer(deltaTime);
@@ -511,7 +511,7 @@ namespace Barotrauma
ignoredBodies.Add(character.AnimController.Collider.FarseerBody);
structureBody = Submarine.PickBody(
SimPosition, attackPosition,
SimPosition, attackSimPos,
ignoredBodies, Physics.CollisionWall);
if (damageTarget is Item)
@@ -520,9 +520,10 @@ namespace Barotrauma
// Ignore blocking on items, because it causes cases where a Mudraptor cannot hit the hatch, for example.
wasHit = true;
}
else if (damageTarget is Structure && structureBody?.UserData is Structure)
else if (damageTarget is Structure wall && structureBody != null &&
(structureBody.UserData is Structure || (structureBody.UserData is Submarine sub && sub == wall.Submarine)))
{
// If the attack is aimed to a structure and hits a structure, it's successful
// If the attack is aimed to a structure (wall) and hits a structure or the sub, it's successful
wasHit = true;
}
else
@@ -607,7 +608,7 @@ namespace Barotrauma
attack.SetCoolDown();
}
Vector2 diff = attackPosition - SimPosition;
Vector2 diff = attackSimPos - SimPosition;
bool applyForces = (!attack.ApplyForcesOnlyOnce || !wasRunning) && diff.LengthSquared() > 0.00001f;
if (applyForces)
{
@@ -620,13 +621,13 @@ namespace Barotrauma
Limb limb = character.AnimController.Limbs[limbIndex];
Vector2 forcePos = limb.pullJoint == null ? limb.body.SimPosition : limb.pullJoint.WorldAnchorA;
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
limb.body.ApplyLinearImpulse(limb.Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
}
}
else
{
Vector2 forcePos = pullJoint == null ? body.SimPosition : pullJoint.WorldAnchorA;
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackPosition - SimPosition), forcePos);
body.ApplyLinearImpulse(Mass * attack.Force * Vector2.Normalize(attackSimPos - SimPosition), forcePos);
}
}
return wasHit;

View File

@@ -912,7 +912,7 @@ namespace Barotrauma
ThrowError(args[0] + " is not a valid latency value.");
return;
}
if (!float.TryParse(args[0], NumberStyles.Any, CultureInfo.InvariantCulture, out float randomLatency))
if (!float.TryParse(args[1], NumberStyles.Any, CultureInfo.InvariantCulture, out float randomLatency))
{
ThrowError(args[1] + " is not a valid latency value.");
return;

View File

@@ -103,12 +103,6 @@ namespace Barotrauma
if (missionType == MissionType.Random)
{
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)
{

View File

@@ -208,7 +208,7 @@ namespace Barotrauma
DockingPort myPort = null, outPostPort = null;
foreach (DockingPort port in DockingPort.List)
{
if (port.IsHorizontal) { continue; }
if (port.IsHorizontal || port.Docked) { continue; }
if (port.Item.Submarine == level.StartOutpost)
{
outPostPort = port;

View File

@@ -168,7 +168,7 @@ namespace Barotrauma
}
}
private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f;
private float soundVolume = 0.5f, musicVolume = 0.3f, voiceChatVolume = 0.5f, microphoneVolume = 1.0f;
public float SoundVolume
{
@@ -211,6 +211,14 @@ namespace Barotrauma
}
}
public float MicrophoneVolume
{
get { return microphoneVolume; }
set
{
microphoneVolume = MathHelper.Clamp(value, 0.1f, 5.0f);
}
}
public string Language
{
get { return TextManager.Language; }

View File

@@ -51,6 +51,11 @@ namespace Barotrauma.Items.Components
public PhysicsBody Body { get; private set; }
private float RepairThreshold
{
get { return item.GetComponent<Repairable>()?.ShowRepairUIThreshold ?? 0.0f; }
}
private float stuck;
[Serialize(0.0f, false)]
public float Stuck

View File

@@ -105,7 +105,7 @@ namespace Barotrauma.Items.Components
GameServer.Log(picker.LogName + " threw " + item.Name, ServerLog.MessageType.ItemInteraction);
#endif
item.Drop(picker);
item.Drop(picker, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f);
ac.GetLimb(LimbType.Head).body.ApplyLinearImpulse(throwVector*10.0f);

View File

@@ -208,8 +208,9 @@ namespace Barotrauma.Items.Components
public ItemComponent(Item item, XElement element)
{
this.item = item;
originalElement = element;
name = element.Name.ToString();
properties = SerializableProperty.GetProperties(this);
SerializableProperties = SerializableProperty.GetProperties(this);
requiredItems = new Dictionary<RelatedItem.RelationType, List<RelatedItem>>();
requiredSkills = new List<Skill>();
@@ -243,18 +244,9 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Invalid pick key in " + element + "!", e);
}
properties = SerializableProperty.DeserializeProperties(this, element);
#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
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
ParseMsg();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
@@ -631,53 +623,11 @@ namespace Barotrauma.Items.Components
public virtual void Load(XElement componentElement)
{
if (componentElement == null) return;
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!properties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
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();
OverrideRequiredItems(componentElement);
}

View File

@@ -201,13 +201,13 @@ namespace Barotrauma.Items.Components
tolerance = MathHelper.Lerp(5.0f, 20.0f, degreeOfSuccess);
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);
allowedTemperature = Vector2.Lerp(new Vector2(30.0f, 70.0f), new Vector2(10.0f, 90.0f), degreeOfSuccess);
float fissionRateTolerance = MathHelper.Lerp(10.0f, 20.0f, degreeOfSuccess);
optimalFissionRate = Vector2.Lerp(new Vector2(40.0f, 70.0f), new Vector2(30.0f, 85.0f), degreeOfSuccess);
allowedFissionRate = Vector2.Lerp(new Vector2(30.0f, 85.0f), new Vector2(20.0f, 98.0f), degreeOfSuccess);
optimalFissionRate = Vector2.Lerp(new Vector2(30, AvailableFuel - 20), new Vector2(20, AvailableFuel - 10), degreeOfSuccess);
optimalFissionRate.X = Math.Min(optimalFissionRate.X, optimalFissionRate.Y - 10);
allowedFissionRate = Vector2.Lerp(new Vector2(20, AvailableFuel), new Vector2(10, AvailableFuel), degreeOfSuccess);
allowedFissionRate.X = Math.Min(allowedFissionRate.X, allowedFissionRate.Y - 10);
float heatAmount = fissionRate * (AvailableFuel / 100.0f) * 2.0f;
float temperatureDiff = (heatAmount - turbineOutput) - Temperature;
@@ -364,7 +364,7 @@ namespace Barotrauma.Items.Components
}
else if (-currPowerConsumption < load)
{
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, allowedFissionRate.Y);
targetFissionRate = Math.Min(targetFissionRate + speed * 2 * deltaTime, 100.0f);
}
targetFissionRate = MathHelper.Clamp(targetFissionRate, 0.0f, 100.0f);
}

View File

@@ -13,8 +13,6 @@ namespace Barotrauma.Items.Components
private string header;
private float fixDurationLowSkill, fixDurationHighSkill;
private float deteriorationTimer;
[Serialize(0.0f, true), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 2, ToolTip = "How fast the condition of the item deteriorates per second.")]
@@ -52,16 +50,19 @@ namespace Barotrauma.Items.Components
set;
}
/*private float repairProgress;
public float RepairProgress
[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.")]
public float FixDurationLowSkill
{
get { return repairProgress; }
set
{
repairProgress = MathHelper.Clamp(value, 0.0f, 1.0f);
if (repairProgress >= 1.0f && currentFixer != null) currentFixer.AnimController.Anim = AnimController.Animation.None;
}
}*/
get;
set;
}
[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.")]
public float FixDurationHighSkill
{
get;
set;
}
private Character currentFixer;
public Character CurrentFixer
@@ -83,8 +84,6 @@ namespace Barotrauma.Items.Components
this.item = item;
header = element.GetAttributeString("name", "");
fixDurationLowSkill = element.GetAttributeFloat("fixdurationlowskill", 100.0f);
fixDurationHighSkill = element.GetAttributeFloat("fixdurationhighskill", 5.0f);
InitProjSpecific(element);
}
@@ -160,7 +159,7 @@ namespace Barotrauma.Items.Components
}
bool wasBroken = !item.IsFullCondition;
float fixDuration = MathHelper.Lerp(fixDurationLowSkill, fixDurationHighSkill, successFactor);
float fixDuration = MathHelper.Lerp(FixDurationLowSkill, FixDurationHighSkill, successFactor);
if (fixDuration <= 0.0f)
{
item.Condition = item.MaxCondition;
@@ -186,26 +185,5 @@ namespace Barotrauma.Items.Components
{
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);
}
}
}

View File

@@ -77,7 +77,7 @@ namespace Barotrauma.Items.Components
IsActive = value;
#if SERVER
if (GameMain.Server != null) item.CreateServerEvent(this);
if (GameMain.Server != null && GameMain.Server.GameStarted) { item.CreateServerEvent(this); }
#endif
}
}

View File

@@ -178,25 +178,20 @@ namespace Barotrauma.Items.Components
newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (nodes.Count > 0 && nodes[0] == 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)
int newNodeIndex = 0;
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);
}

View File

@@ -433,9 +433,10 @@ namespace Barotrauma.Items.Components
if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill"))
{
ItemContainer container = null;
Item containerItem = null;
foreach (MapEntity e in item.linkedTo)
{
var containerItem = e as Item;
containerItem = e as Item;
if (containerItem == null) continue;
container = containerItem.GetComponent<ItemContainer>();
@@ -453,7 +454,7 @@ namespace Barotrauma.Items.Components
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);
containShellObjective.MinContainedAmount = usableProjectileCount + 1;
containShellObjective.IgnoreAlreadyContainedItems = true;
containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
objective.AddSubObjective(containShellObjective);
return false;
}

View File

@@ -60,7 +60,6 @@ namespace Barotrauma
public readonly XElement StaticBodyConfig;
private bool needsPositionUpdate;
private float lastSentCondition;
private float sendConditionUpdateTimer;
private bool conditionUpdatePending;
@@ -89,7 +88,7 @@ namespace Barotrauma
if (hasInGameEditableProperties == null)
{
hasInGameEditableProperties = false;
if (properties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
if (SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
{
hasInGameEditableProperties = true;
}
@@ -98,7 +97,7 @@ namespace Barotrauma
foreach (ItemComponent component in components)
{
if (!component.AllowInGameEditing) { continue; }
if (component.properties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
if (component.SerializableProperties.Values.Any(p => p.Attributes.OfType<InGameEditable>().Any()))
{
hasInGameEditableProperties = true;
break;
@@ -212,14 +211,14 @@ namespace Barotrauma
set { spriteColor = value; }
}
[Serialize("1.0,1.0,1.0,1.0", false), Editable]
[Serialize("1.0,1.0,1.0,1.0", true), Editable]
public Color InventoryIconColor
{
get;
protected set;
}
[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.")]
[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.")]
public Color ContainerColor
{
get;
@@ -275,12 +274,11 @@ namespace Barotrauma
SetActiveSprite();
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && lastSentCondition != condition)
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsServer && !MathUtils.NearlyEqual(lastSentCondition, condition))
{
if (Math.Abs(lastSentCondition - condition) > 1.0f || condition == 0.0f || condition == Prefab.Health)
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.Status });
lastSentCondition = condition;
conditionUpdatePending = true;
}
}
}
@@ -585,10 +583,10 @@ namespace Barotrauma
public override MapEntity Clone()
{
Item clone = new Item(rect, Prefab, Submarine, callOnItemLoaded: false);
foreach (KeyValuePair<string, SerializableProperty> property in properties)
foreach (KeyValuePair<string, SerializableProperty> property in SerializableProperties)
{
if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
clone.properties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
clone.SerializableProperties[property.Key].TrySetValue(clone, property.Value.GetValue(this));
}
if (components.Count != clone.components.Count)
@@ -605,7 +603,7 @@ namespace Barotrauma
foreach (KeyValuePair<string, SerializableProperty> property in components[i].SerializableProperties)
{
if (!property.Value.Attributes.OfType<Editable>().Any()) continue;
clone.components[i].properties[property.Key].TrySetValue(clone.components[i], property.Value.GetValue(components[i]));
clone.components[i].SerializableProperties[property.Key].TrySetValue(clone.components[i], property.Value.GetValue(components[i]));
}
//clone requireditem identifiers
@@ -998,6 +996,21 @@ namespace Barotrauma
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);
foreach (ItemComponent ic in components)
@@ -1273,7 +1286,11 @@ namespace Barotrauma
LastSentSignalRecipients.Clear();
if (connections == null) return;
stepsTaken++;
public List<T> GetConnectedComponentsRecursive<T>(Connection c) where T : ItemComponent
{
List<T> connectedComponents = new List<T>();
List<Item> alreadySearched = new List<Item>() { this };
GetConnectedComponentsRecursive(c, alreadySearched, connectedComponents);
if (!connections.TryGetValue(connectionName, out Connection c)) return;
@@ -1561,12 +1578,15 @@ namespace Barotrauma
return isCombined;
}
public void Drop(Character dropper)
public void Drop(Character dropper, bool createNetworkEvent = true)
{
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
if (createNetworkEvent)
{
parentInventory.CreateNetworkEvent();
if (parentInventory != null && !parentInventory.Owner.Removed && !Removed &&
GameMain.NetworkMember != null && (GameMain.NetworkMember.IsServer || Character.Controlled == dropper))
{
parentInventory.CreateNetworkEvent();
}
}
foreach (ItemComponent ic in components) { ic.Drop(dropper); }
@@ -1811,6 +1831,18 @@ namespace Barotrauma
partial void UpdateNetPosition(float deltaTime);
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 identifier = element.GetAttributeString("identifier", "");
@@ -1856,9 +1888,16 @@ namespace Barotrauma
linkedToID = new List<ushort>()
};
#if SERVER
if (createNetworkEvent)
{
Spawner.CreateNetworkEvent(item, remove: false);
}
#endif
foreach (XAttribute attribute in element.Attributes())
{
if (!item.properties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue;
if (!item.SerializableProperties.TryGetValue(attribute.Name.ToString(), out SerializableProperty property)) continue;
bool shouldBeLoaded = false;
foreach (var propertyAttribute in property.Attributes.OfType<Serialize>())
{

View File

@@ -91,8 +91,7 @@ namespace Barotrauma
public override void CreateNetworkEvent()
{
int componentIndex = container.Item.GetComponentIndex(container);
if (componentIndex == -1)
if (!Item.ItemList.Contains(container.Item))
{
string errorMsg = "Attempted to create a network event for an item (" + container.Item.Name + ") that hasn't been fully initialized yet.";
DebugConsole.ThrowError(errorMsg);
@@ -102,6 +101,13 @@ namespace Barotrauma
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.IsClient) { syncItemsDelay = 1.0f; }

View File

@@ -276,6 +276,9 @@ namespace Barotrauma
Hull hull = Hull.FindHull(ConvertUnits.ToDisplayUnits(explosionPos), null, false);
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);
Dictionary<Limb, float> distFactors = new Dictionary<Limb, float>();

View File

@@ -176,11 +176,10 @@ namespace Barotrauma
UpdateProjSpecific(growModifier);
#if CLIENT
if (GameMain.Client != null) return;
#endif
if (size.X < 1.0f) Remove();
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
Remove();
}
}
partial void UpdateProjSpecific(float growModifier);
@@ -293,10 +292,6 @@ namespace Barotrauma
//evaporate some of the water
hull.WaterVolume -= extinguishAmount;
#if CLIENT
if (GameMain.Client != null) return;
#endif
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
Remove();
@@ -326,11 +321,10 @@ namespace Barotrauma
hull.WaterVolume -= extinguishAmount;
#if CLIENT
if (GameMain.Client != null) return;
#endif
if (size.X < 1.0f) Remove();
if (size.X < 1.0f && (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer))
{
Remove();
}
}
public void Extinguish(float deltaTime, float amount, Vector2 worldPosition)

View File

@@ -231,7 +231,7 @@ namespace Barotrauma
public static Level CreateRandom(LocationConnection locationConnection)
{
string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name;
string seed = locationConnection.Locations[0].BaseName + locationConnection.Locations[1].BaseName;
float sizeFactor = MathUtils.InverseLerp(
MapGenerationParams.Instance.SmallLevelConnectionLength,
@@ -1522,14 +1522,40 @@ namespace Barotrauma
outpost.MakeOutpost();
Point? minSize = null;
DockingPort subPort = null;
if (Submarine.MainSub != null)
{
Point subSize = Submarine.MainSub.GetDockedBorders().Size;
Point outpostSize = outpost.GetDockedBorders().Size;
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;
}
}
}
outpost.SetPosition(outpost.FindSpawnPos(i == 0 ? StartPosition : EndPosition, minSize));
float subDockingPortOffset = subPort == null ? 0.0f : subPort.Item.WorldPosition.X - Submarine.MainSub.WorldPosition.X;
//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)
{
StartOutpost = outpost;

View File

@@ -15,7 +15,6 @@ namespace Barotrauma
public Vector3 Position;
public float NetworkUpdateTimer;
public const float NetworkUpdateInterval = 0.2f;
public float Scale;

View File

@@ -343,7 +343,7 @@ namespace Barotrauma
{
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { obj });
obj.NeedsNetworkSyncing = false;
obj.NetworkUpdateTimer = LevelObject.NetworkUpdateInterval;
obj.NetworkUpdateTimer = NetConfig.LevelObjectUpdateInterval;
}
}

View File

@@ -432,12 +432,16 @@ namespace Barotrauma
{
if (ForceFluctuationStrength > 0.0f)
{
forceFluctuationTimer += deltaTime;
if (forceFluctuationTimer > ForceFluctuationInterval)
//no need for force fluctuation (or network updates) if the trigger limits velocity and there are no triggerers
if (forceMode != TriggerForceMode.LimitVelocity || triggerers.Any())
{
NeedsNetworkSyncing = true;
currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
forceFluctuationTimer = 0.0f;
forceFluctuationTimer += deltaTime;
if (forceFluctuationTimer > ForceFluctuationInterval)
{
NeedsNetworkSyncing = true;
currentForceFluctuation = Rand.Range(1.0f - ForceFluctuationStrength, 1.0f);
forceFluctuationTimer = 0.0f;
}
}
}

View File

@@ -15,6 +15,8 @@ namespace Barotrauma
public int TypeChangeTimer;
public string BaseName { get => baseName; }
public string Name { get; private set; }
public Vector2 MapPosition { get; private set; }
@@ -34,7 +36,7 @@ namespace Barotrauma
for (int i = availableMissions.Count; i < Connections.Count * 2; i++)
{
int seed = (ToolBox.StringToInt(Name) + MissionsCompleted * 10 + i) % int.MaxValue;
int seed = (ToolBox.StringToInt(BaseName) + MissionsCompleted * 10 + i) % int.MaxValue;
MTRandom rand = new MTRandom(seed);
LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count];
@@ -45,7 +47,7 @@ namespace Barotrauma
if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; }
if (GameSettings.VerboseLogging && mission != null)
{
DebugConsole.NewMessage("Generated a new mission for a location connection (seed: " + seed.ToString("X") + ", type: " + mission.Name + ")", Color.White);
DebugConsole.NewMessage("Generated a new mission for a location (location: " + Name + ", seed: " + seed.ToString("X") + ", missions completed: " + MissionsCompleted + ", type: " + mission.Name + ")", Color.White);
}
availableMissions.Add(mission);
}
@@ -98,7 +100,16 @@ namespace Barotrauma
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;
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
@@ -110,6 +121,7 @@ namespace Barotrauma
{
if (mission.Completed)
{
DebugConsole.Log("Mission \"" + mission.Name + "\" completed in \"" + Name + "\".");
MissionsCompleted++;
}
}
@@ -117,7 +129,7 @@ namespace Barotrauma
availableMissions.RemoveAll(m => m.Completed);
}
private string RandomName(LocationType type)
public void Remove()
{
baseName = type.GetRandomName();
nameFormatIndex = Rand.Int(type.NameFormats.Count, Rand.RandSync.Server);

View File

@@ -578,10 +578,12 @@ namespace Barotrauma
location.MissionsCompleted = missionsCompleted;
if (showNotifications && prevLocationType != location.Type)
{
ChangeLocationType(
location,
prevLocationName,
prevLocationType.CanChangeTo.Find(c => c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant()));
var change = prevLocationType.CanChangeTo.Find(c =>
c.ChangeToType.ToLowerInvariant() == location.Type.Identifier.ToLowerInvariant());
if (change != null)
{
ChangeLocationType(location, prevLocationName, change);
}
}
break;
case "connection":

View File

@@ -407,7 +407,7 @@ namespace Barotrauma
try
{
MethodInfo loadMethod = t.GetMethod("Load");
MethodInfo loadMethod = t.GetMethod("Load", new [] { typeof(XElement), typeof(Submarine) });
if (loadMethod == null)
{
DebugConsole.ThrowError("Could not find the method \"Load\" in " + t + ".");

View File

@@ -298,6 +298,12 @@ namespace Barotrauma
}
}
// Only add ai targets automatically to walls
if (aiTarget == null && HasBody && Tags.Contains("wall"))
{
aiTarget = new AITarget(this);
}
InsertToList();
}

View File

@@ -8,6 +8,8 @@ namespace Barotrauma
{
partial class StructurePrefab : MapEntityPrefab
{
public XElement ConfigElement { get; private set; }
private bool canSpriteFlipX, canSpriteFlipY;
private float health;
@@ -150,6 +152,7 @@ namespace Barotrauma
{
name = element.GetAttributeString("name", "")
};
sp.ConfigElement = element;
if (string.IsNullOrEmpty(sp.name)) sp.name = element.Name.ToString();
sp.identifier = element.GetAttributeString("identifier", "");
@@ -214,6 +217,10 @@ namespace Barotrauma
}
SerializableProperty.DeserializeProperties(sp, element);
if (sp.Body)
{
sp.Tags.Add("wall");
}
string translatedDescription = TextManager.Get("EntityDescription." + sp.identifier, true);
if (!string.IsNullOrEmpty(translatedDescription)) sp.Description = translatedDescription;

View File

@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using Voronoi2;
@@ -322,7 +323,7 @@ namespace Barotrauma
for (int i = 0; i <= maxLoadRetries; i++)
{
doc = OpenFile(filePath);
if (doc != null || i == maxLoadRetries || !File.Exists(filePath)) { break; }
if (doc != null || i == maxLoadRetries) { break; }
DebugConsole.NewMessage("Opening submarine file \"" + filePath + "\" failed, retrying in 250 ms...");
Thread.Sleep(250);
}
@@ -493,7 +494,7 @@ namespace Barotrauma
}
}
public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null)
public Vector2 FindSpawnPos(Vector2 spawnPos, Point? submarineSize = null, float subDockingPortOffset = 0.0f)
{
Rectangle dockedBorders = GetDockedBorders();
Vector2 diffFromDockedBorders =
@@ -541,17 +542,17 @@ namespace Barotrauma
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f;
spawnPos.X = maxX - minWidth - 100.0f + subDockingPortOffset;
}
else if (maxX > Level.Loaded.Size.X)
{
//no wall found at right side, spawn to the right from the left-side wall
spawnPos.X = minX + minWidth + 100.0f;
spawnPos.X = minX + minWidth + 100.0f + subDockingPortOffset;
}
else
{
//walls found at both sides, use their midpoint
spawnPos.X = (minX + maxX) / 2;
spawnPos.X = (minX + maxX) / 2 + subDockingPortOffset;
}
spawnPos.Y = Math.Min(spawnPos.Y, Level.Loaded.Size.Y - dockedBorders.Height / 2 - 10);
@@ -1191,7 +1192,7 @@ namespace Barotrauma
for (int i = 0; i <= maxLoadRetries; i++)
{
doc = OpenFile(filePath);
if (doc != null || i == maxLoadRetries || !File.Exists(filePath)) { break; }
if (doc != null || i == maxLoadRetries) { break; }
DebugConsole.NewMessage("Loading the submarine \"" + Name + "\" failed, retrying in 250 ms...");
Thread.Sleep(250);
}

View File

@@ -700,6 +700,23 @@ namespace Barotrauma
Vector2 impulse = direction * impact * 0.5f;
impulse = impulse.ClampLength(5.0f);
if (!MathUtils.IsValid(impulse))
{
string errorMsg =
"Invalid impulse in SubmarineBody.ApplyImpact: " + impulse +
". Direction: " + direction + ", body position: " + Body.SimPosition + ", impact: " + impact + ".";
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"SubmarineBody.ApplyImpact:InvalidImpulse",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
return;
}
#if CLIENT
if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{

View File

@@ -129,10 +129,10 @@ namespace Barotrauma.Networking
if (listener.WorldPosition == sender.WorldPosition) { return 0.0f; }
float dist = Vector2.Distance(listener.WorldPosition, sender.WorldPosition);
if (dist > range) { return 0.0f; }
if (dist > range) { return 1.0f; }
if (Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null) dist = (dist + 100f) * obstructionmult;
if (dist > range) { return 0.0f; }
if (dist > range) { return 1.0f; }
return dist / range;
}
@@ -152,7 +152,7 @@ namespace Barotrauma.Networking
public static string ApplyDistanceEffect(string text, float garbleAmount)
{
if (garbleAmount < 0.3f) return text;
if (garbleAmount > 1.0f) return "";
if (garbleAmount >= 1.0f) return "";
int startIndex = Math.Max(text.IndexOf(':') + 1, 1);

View File

@@ -7,7 +7,7 @@
enum FileTransferMessageType
{
Unknown, Initiate, Data, Cancel
Unknown, Initiate, Data, TransferOnSameMachine, Cancel
}
enum FileTransferType

View File

@@ -32,11 +32,14 @@ namespace Barotrauma.Networking
public const float HighPrioCharacterPositionUpdateInterval = 0.0f;
public const float LowPrioCharacterPositionUpdateInterval = 1.0f;
//how much the physics body of an item has to move until the server
//send a position update to clients (in sim units)
public const float ItemPosUpdateDistance = 2.0f;
public const float DeleteDisconnectedTime = 20.0f;
public const float DeleteDisconnectedTime = 10.0f;
public const float ItemConditionUpdateInterval = 0.15f;
public const float LevelObjectUpdateInterval = 0.5f;
public const float HullUpdateInterval = 0.5f;
public const float HullUpdateDistance = 20000.0f;
public const int MaxEventPacketsPerUpdate = 4;
/// <summary>
/// Interpolates the positional error of a physics body towards zero.

View File

@@ -58,15 +58,8 @@ namespace Barotrauma.Networking
eventCount++;
continue;
}
//the ID has been taken by another entity (the original entity has been removed) -> write an empty event
/*else if (Entity.FindEntityByID(e.Entity.ID) != e.Entity || e.Entity.IdFreed)
{
//technically the clients don't have any use for these, but removing events and shifting the IDs of all
//consecutive ones is so error-prone that I think this is a safer option
tempBuffer.Write(Entity.NullEntityID);
tempBuffer.WritePadBits();
}*/
else
if (msg.LengthBytes + tempBuffer.LengthBytes + tempEventBuffer.LengthBytes > MaxEventBufferLength)
{
//no more room in this packet
break;

View File

@@ -513,11 +513,29 @@ namespace Barotrauma.Networking
set;
}
private SelectionMode subSelectionMode;
[Serialize(SelectionMode.Manual, true)]
public SelectionMode SubSelectionMode { get; private set; }
public SelectionMode SubSelectionMode
{
get { return subSelectionMode; }
set
{
subSelectionMode = value;
Voting.AllowSubVoting = subSelectionMode == SelectionMode.Vote;
}
}
private SelectionMode modeSelectionMode;
[Serialize(SelectionMode.Manual, true)]
public SelectionMode ModeSelectionMode { get; private set; }
public SelectionMode ModeSelectionMode
{
get { return modeSelectionMode; }
set
{
modeSelectionMode = value;
Voting.AllowModeVoting = modeSelectionMode == SelectionMode.Vote;
}
}
public BanList BanList { get; private set; }

View File

@@ -36,392 +36,20 @@ namespace Barotrauma
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public Vector2 LinearVelocity
{
get;
private set;
}
public float AngularVelocity
{
get;
private set;
}
public readonly float Timestamp;
public readonly UInt16 ID;
public PosInfo(Vector2 pos, float rotation, Vector2 linearVelocity, float angularVelocity, float time)
public PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, float time)
: this(pos, rotation, linearVelocity, angularVelocity, 0, time)
{
}
public PosInfo(Vector2 pos, float rotation, Vector2 linearVelocity, float angularVelocity, UInt16 ID)
public PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID)
: this(pos, rotation, linearVelocity, angularVelocity, ID, 0.0f)
{
}
protected PosInfo(Vector2 pos, float rotation, Vector2 linearVelocity, float angularVelocity, UInt16 ID, float time)
protected PosInfo(Vector2 pos, float? rotation, Vector2 linearVelocity, float? angularVelocity, UInt16 ID, float time)
{
Position = pos;
Rotation = rotation;
@@ -1147,8 +775,8 @@ namespace Barotrauma
newVelocity = positionBuffer[0].LinearVelocity;
newPosition = positionBuffer[0].Position;
newRotation = positionBuffer[0].Rotation;
newAngularVelocity = positionBuffer[0].AngularVelocity;
newRotation = positionBuffer[0].Rotation ?? Rotation;
newAngularVelocity = positionBuffer[0].AngularVelocity ?? AngularVelocity;
positionBuffer.RemoveAt(0);
}

View File

@@ -107,7 +107,7 @@ namespace Barotrauma
public string[] propertyNames;
private object[] propertyEffects;
private PropertyConditional.Comparison conditionalComparison = PropertyConditional.Comparison.And;
private PropertyConditional.Comparison conditionalComparison = PropertyConditional.Comparison.Or;
private List<PropertyConditional> propertyConditionals;
private bool setValue;
@@ -465,6 +465,13 @@ namespace Barotrauma
if (target == null || target.SerializableProperties == null) { continue; }
foreach (PropertyConditional pc in propertyConditionals)
{
if (!string.IsNullOrEmpty(pc.TargetItemComponentName))
{
if (!(target is ItemComponent ic) || ic.Name != pc.TargetItemComponentName)
{
continue;
}
}
if (pc.Matches(target)) { return true; }
}
}
@@ -475,6 +482,13 @@ namespace Barotrauma
if (target == null || target.SerializableProperties == null) { continue; }
foreach (PropertyConditional pc in propertyConditionals)
{
if (!string.IsNullOrEmpty(pc.TargetItemComponentName))
{
if (!(target is ItemComponent ic) || ic.Name != pc.TargetItemComponentName)
{
continue;
}
}
if (!pc.Matches(target)) { return false; }
}
}
@@ -696,6 +710,8 @@ namespace Barotrauma
foreach (Limb limb in character.AnimController.Limbs)
{
limb.character.DamageLimb(entity.WorldPosition, limb, new List<Affliction>() { multipliedAffliction }, stun: 0.0f, playSound: false, attackImpulse: 0.0f);
//only apply non-limb-specific afflictions to the first limb
if (!affliction.Prefab.LimbSpecific) { break; }
}
}
else if (target is Limb limb)

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