f8b0295...0671290

This commit is contained in:
Joonas Rikkonen
2019-03-18 22:59:45 +02:00
parent 23687fbf2f
commit 63eb4d64e5
103 changed files with 1378 additions and 4692 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

@@ -31,6 +31,7 @@
<BootstrapperEnabled>true</BootstrapperEnabled> <BootstrapperEnabled>true</BootstrapperEnabled>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
<ApplicationIcon>..\BarotraumaShared\IconBmp.bmp</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon> <ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
@@ -103,6 +104,7 @@
<Content Include="OpenTK.dll.config"> <Content Include="OpenTK.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Shaders\solidcolor_opengl.fx" />
<Content Include="SharpFont.dll.config"> <Content Include="SharpFont.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -159,6 +161,9 @@
<Content Include="Shaders\losshader_opengl.fx" /> <Content Include="Shaders\losshader_opengl.fx" />
<Content Include="Shaders\postprocess_opengl.fx" /> <Content Include="Shaders\postprocess_opengl.fx" />
<Content Include="Shaders\watershader_opengl.fx" /> <Content Include="Shaders\watershader_opengl.fx" />
<None Include="Content\Effects\solidcolor_opengl.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Shaders\Content_opengl.mgcb" /> <None Include="Shaders\Content_opengl.mgcb" />
<None Include="Content\Effects\blurshader_opengl.xnb"> <None Include="Content\Effects\blurshader_opengl.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -187,6 +192,11 @@
<None Include="packages.config" /> <None Include="packages.config" />
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource>
</ItemGroup>
<Import Project="ClientCode.projitems" Label="Shared" /> <Import Project="ClientCode.projitems" Label="Shared" />
<Import Project="..\BarotraumaShared\SharedCode.projitems" Label="Shared" /> <Import Project="..\BarotraumaShared\SharedCode.projitems" Label="Shared" />
<Import Project="..\BarotraumaShared\SharedContent.projitems" Label="Shared" /> <Import Project="..\BarotraumaShared\SharedContent.projitems" Label="Shared" />
@@ -103,6 +103,7 @@
<Content Include="OpenTK.dll.config"> <Content Include="OpenTK.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="Shaders\solidcolor_opengl.fx" />
<Content Include="SharpFont.dll.config"> <Content Include="SharpFont.dll.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -159,6 +160,9 @@
<Content Include="Shaders\losshader_opengl.fx" /> <Content Include="Shaders\losshader_opengl.fx" />
<Content Include="Shaders\postprocess_opengl.fx" /> <Content Include="Shaders\postprocess_opengl.fx" />
<Content Include="Shaders\watershader_opengl.fx" /> <Content Include="Shaders\watershader_opengl.fx" />
<None Include="Content\Effects\solidcolor_opengl.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Shaders\Content_opengl.mgcb" /> <None Include="Shaders\Content_opengl.mgcb" />
<None Include="Content\Effects\blurshader_opengl.xnb"> <None Include="Content\Effects\blurshader_opengl.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.8.9.5")] [assembly: AssemblyVersion("0.8.9.6")]
[assembly: AssemblyFileVersion("0.8.9.5")] [assembly: AssemblyFileVersion("0.8.9.6")]
@@ -0,0 +1,57 @@
#----------------------------- Global Properties ----------------------------#
/outputDir:bin/$(Platform)
/intermediateDir:obj/$(Platform)
/platform:Windows
/config:
/profile:Reach
/compress:False
#-------------------------------- References --------------------------------#
#---------------------------------- Content ---------------------------------#
#begin blurshader.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:blurshader.fx
#begin damageshader.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:damageshader.fx
#begin deformshader.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:deformshader.fx
#begin losshader.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:losshader.fx
#begin postprocess.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:postprocess.fx
#begin watershader.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:watershader.fx
#begin solidcolor.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:solidcolor.fx
@@ -0,0 +1,62 @@
#----------------------------- Global Properties ----------------------------#
/outputDir:bin/$(Platform)
/intermediateDir:obj/$(Platform)
/platform:DesktopGL
/config:
/profile:Reach
/compress:False
#-------------------------------- References --------------------------------#
#---------------------------------- Content ---------------------------------#
#begin blurshader_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:blurshader_opengl.fx
#begin Content_opengl.mgcb
/importer:
/processor:
/build:Content_opengl.mgcb
#begin damageshader_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:damageshader_opengl.fx
#begin deformshader_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:deformshader_opengl.fx
#begin losshader_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:losshader_opengl.fx
#begin postprocess_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:postprocess_opengl.fx
#begin solidcolor_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:solidcolor_opengl.fx
#begin watershader_opengl.fx
/importer:EffectImporter
/processor:EffectProcessor
/processorParam:DebugMode=Auto
/build:watershader_opengl.fx
@@ -0,0 +1,38 @@
Texture2D xTexture;
sampler TextureSampler = sampler_state { Texture = <xTexture>; };
float blurDistance;
float4 color;
float4 solidColor(float4 position : SV_Position, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float a = tex2D(TextureSampler, texCoord).a;
return color * a;
}
float4 solidColorBlur(float4 position : SV_Position, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float sample;
sample = tex2D(TextureSampler, float2(texCoord.x + blurDistance, texCoord.y + blurDistance)).a;
sample += tex2D(TextureSampler, float2(texCoord.x - blurDistance, texCoord.y - blurDistance)).a;
sample += tex2D(TextureSampler, float2(texCoord.x + blurDistance, texCoord.y - blurDistance)).a;
sample += tex2D(TextureSampler, float2(texCoord.x - blurDistance, texCoord.y + blurDistance)).a;
sample = sample * 0.25f;
return color * sample;
}
technique SolidColor
{
pass Pass1
{
PixelShader = compile ps_4_0_level_9_1 solidColor();
}
}
technique SolidColorBlur
{
pass Pass1
{
PixelShader = compile ps_4_0_level_9_1 solidColorBlur();
}
}
@@ -0,0 +1,38 @@
Texture xTexture;
sampler TextureSampler = sampler_state { Texture = <xTexture>; };
float blurDistance;
float4 color;
float4 solidColor(float4 position : SV_Position, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float a = tex2D(TextureSampler, texCoord).a;
return color * a;
}
float4 solidColorBlur(float4 position : SV_Position, float4 clr : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
float sample;
sample = tex2D(TextureSampler, float2(texCoord.x + blurDistance, texCoord.y + blurDistance)).a;
sample += tex2D(TextureSampler, float2(texCoord.x - blurDistance, texCoord.y - blurDistance)).a;
sample += tex2D(TextureSampler, float2(texCoord.x + blurDistance, texCoord.y - blurDistance)).a;
sample += tex2D(TextureSampler, float2(texCoord.x - blurDistance, texCoord.y + blurDistance)).a;
sample = sample * 0.25f;
return color * sample;
}
technique SolidColor
{
pass Pass1
{
PixelShader = compile ps_3_0 solidColor();
}
}
technique SolidColorBlur
{
pass Pass1
{
PixelShader = compile ps_3_0 solidColorBlur();
}
}
@@ -242,7 +242,7 @@ namespace Barotrauma
GUI.Style.FocusIndicator.Draw(spriteBatch, GUI.Style.FocusIndicator.Draw(spriteBatch,
(int)((focusedItemOverlayTimer - 1.0f) * GUI.Style.FocusIndicator.FrameCount * 3.0f), (int)((focusedItemOverlayTimer - 1.0f) * GUI.Style.FocusIndicator.FrameCount * 3.0f),
circlePos, circlePos,
Color.Orange * 0.3f, Color.LightBlue * 0.3f,
origin: GUI.Style.FocusIndicator.FrameSize.ToVector2() / 2, origin: GUI.Style.FocusIndicator.FrameSize.ToVector2() / 2,
rotate: (float)Timing.TotalTime, rotate: (float)Timing.TotalTime,
scale: scale); scale: scale);
@@ -275,8 +275,6 @@ namespace Barotrauma
textPos.Y += 20; textPos.Y += 20;
} }
} }
} }
foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values) foreach (HUDProgressBar progressBar in character.HUDProgressBars.Values)
@@ -544,7 +544,10 @@ namespace Barotrauma
{ {
healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(Vitality / MaxVitality, Color.Red, Color.Orange, Color.Green); healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(Vitality / MaxVitality, Color.Red, Color.Orange, Color.Green);
healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f; healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f;
healthBar.BarSize = healthWindowHealthBar.BarSize = (Vitality > 0.0f) ? Vitality / MaxVitality : 1.0f - Vitality / MinVitality; healthBar.BarSize = healthWindowHealthBar.BarSize =
(Vitality > 0.0f) ?
(MaxVitality > 0.0f ? Vitality / MaxVitality : 0.0f) :
(Math.Abs(MinVitality) > 0.0f ? 1.0f - Vitality / MinVitality : 0.0f);
if (healthBarPulsateTimer > 0.0f) if (healthBarPulsateTimer > 0.0f)
{ {
@@ -363,7 +363,7 @@ namespace Barotrauma
character.ID = id; character.ID = id;
character.TeamID = (TeamType)teamID; character.TeamID = (TeamType)teamID;
if (configPath == HumanConfigFile) if (configPath == HumanConfigFile && character.TeamID != TeamType.FriendlyNPC)
{ {
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID); CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo); GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
File diff suppressed because it is too large Load Diff
@@ -210,12 +210,7 @@ namespace Barotrauma
public void SetWindowMode(WindowMode windowMode) public void SetWindowMode(WindowMode windowMode)
{ {
WindowMode = windowMode; WindowMode = windowMode;
#if !(OSX)
GraphicsDeviceManager.HardwareModeSwitch = Config.WindowMode != WindowMode.BorderlessWindowed; GraphicsDeviceManager.HardwareModeSwitch = Config.WindowMode != WindowMode.BorderlessWindowed;
#else
// Force borderless on macOS.
GraphicsDeviceManager.HardwareModeSwitch = Config.WindowMode != WindowMode.BorderlessWindowed && Config.WindowMode != WindowMode.Fullscreen;
#endif
GraphicsDeviceManager.IsFullScreen = Config.WindowMode == WindowMode.Fullscreen || Config.WindowMode == WindowMode.BorderlessWindowed; GraphicsDeviceManager.IsFullScreen = Config.WindowMode == WindowMode.Fullscreen || Config.WindowMode == WindowMode.BorderlessWindowed;
Window.IsBorderless = !GraphicsDeviceManager.HardwareModeSwitch; Window.IsBorderless = !GraphicsDeviceManager.HardwareModeSwitch;
@@ -61,120 +61,9 @@ namespace Barotrauma
public CrewManager(XElement element, bool isSinglePlayer) public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer) : this(isSinglePlayer)
{ {
guiFrame = new GUIFrame(new RectTransform(Vector2.One, GUICanvas.Instance), null, Color.Transparent) foreach (XElement subElement in element.Elements())
{ {
CanBeFocused = false if (subElement.Name.ToString().ToLowerInvariant() != "character") continue;
};
Point scrollButtonSize = new Point((int)(200 * GUI.Scale), (int)(30 * GUI.Scale));
crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), "", Color.Transparent)
{
CanBeFocused = false
};
toggleCrewButton = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform)
{ AbsoluteOffset = HUDLayoutSettings.CrewArea.Location },
"", style: "UIToggleButton");
toggleCrewButton.OnClicked += (GUIButton btn, object userdata) =>
{
toggleCrewAreaOpen = !toggleCrewAreaOpen;
foreach (GUIComponent child in btn.Children)
{
child.SpriteEffects = toggleCrewAreaOpen ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
}
return true;
};
characterListBox = new GUIListBox(new RectTransform(new Point(100, (int)(crewArea.Rect.Height - scrollButtonSize.Y * 1.6f)), crewArea.RectTransform, Anchor.CenterLeft), false, Color.Transparent, null)
{
//Spacing = (int)(3 * GUI.Scale),
ScrollBarEnabled = false,
ScrollBarVisible = false,
CanBeFocused = false
};
scrollButtonUp = new GUIButton(new RectTransform(scrollButtonSize, crewArea.RectTransform, Anchor.TopLeft, Pivot.TopLeft), "", Alignment.Center, "GUIButtonVerticalArrow")
{
Visible = false,
UserData = -1,
OnClicked = ScrollCharacterList
};
scrollButtonDown = new GUIButton(new RectTransform(scrollButtonSize, crewArea.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft), "", Alignment.Center, "GUIButtonVerticalArrow")
{
Visible = false,
UserData = 1,
OnClicked = ScrollCharacterList
};
scrollButtonDown.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipVertically);
if (isSinglePlayer)
{
chatBox = new ChatBox(guiFrame, isSinglePlayer: true)
{
OnEnterMessage = (textbox, text) =>
{
if (Character.Controlled == null) { return true; }
textbox.TextColor = ChatMessage.MessageColor[(int)ChatMessageType.Default];
if (!string.IsNullOrWhiteSpace(text))
{
string msgCommand = ChatMessage.GetChatMessageCommand(text, out string msg);
AddSinglePlayerChatMessage(
Character.Controlled.Info.Name,
msg,
((msgCommand == "r" || msgCommand == "radio") && ChatMessage.CanUseRadio(Character.Controlled)) ? ChatMessageType.Radio : ChatMessageType.Default,
Character.Controlled);
var headset = GetHeadset(Character.Controlled, true);
if (headset != null && headset.CanTransmit())
{
headset.TransmitSignal(stepsTaken: 0, signal: msg, source: headset.Item, sender: Character.Controlled, sendToChat: false);
}
}
textbox.Deselect();
textbox.Text = "";
return true;
}
};
chatBox.InputBox.OnTextChanged += chatBox.TypingChatMessage;
}
var reports = Order.PrefabList.FindAll(o => o.TargetAllCharacters && o.SymbolSprite != null);
reportButtonFrame = new GUILayoutGroup(new RectTransform(
new Point((HUDLayoutSettings.CrewArea.Height - (int)((reports.Count - 1) * 5 * GUI.Scale)) / reports.Count, HUDLayoutSettings.CrewArea.Height), guiFrame.RectTransform))
{
AbsoluteSpacing = (int)(5 * GUI.Scale),
UserData = "reportbuttons",
CanBeFocused = false
};
//report buttons
foreach (Order order in reports)
{
if (!order.TargetAllCharacters || order.SymbolSprite == null) continue;
var btn = new GUIButton(new RectTransform(new Point(reportButtonFrame.Rect.Width), reportButtonFrame.RectTransform), style: null)
{
OnClicked = (GUIButton button, object userData) =>
{
if (Character.Controlled == null || Character.Controlled.SpeechImpediment >= 100.0f) return false;
SetCharacterOrder(null, order, null, Character.Controlled);
HumanAIController.PropagateHullSafety(Character.Controlled, Character.Controlled.CurrentHull);
return true;
},
UserData = order,
ToolTip = order.Name
};
new GUIFrame(new RectTransform(new Vector2(1.5f), btn.RectTransform, Anchor.Center), "OuterGlow")
{
Color = Color.Red * 0.8f,
HoverColor = Color.Red * 1.0f,
PressedColor = Color.Red * 0.6f,
UserData = "highlighted",
CanBeFocused = false,
Visible = false
};
var characterInfo = new CharacterInfo(subElement); var characterInfo = new CharacterInfo(subElement);
characterInfos.Add(characterInfo); characterInfos.Add(characterInfo);
@@ -185,20 +74,6 @@ namespace Barotrauma
break; break;
} }
} }
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = GUI.Scale;
}
#endregion
#region Character list management
public Rectangle GetCharacterListArea()
{
return characterListBox.Rect;
} }
partial void InitProjectSpecific() partial void InitProjectSpecific()
@@ -713,7 +588,7 @@ namespace Barotrauma
characterListBox.BarScroll -= characterListBox.BarScroll % step; characterListBox.BarScroll -= characterListBox.BarScroll % step;
characterListBox.BarScroll += dir * step; characterListBox.BarScroll += dir * step;
return radioItem.GetComponent<WifiComponent>(); return false;
} }
private IEnumerable<object> KillCharacterAnim(GUIComponent component) private IEnumerable<object> KillCharacterAnim(GUIComponent component)
@@ -726,12 +601,6 @@ namespace Barotrauma
{ {
comp.Color = Color.DarkRed; comp.Color = Color.DarkRed;
} }
List<Character> availableSpeakers = Character.CharacterList.FindAll(c =>
c.AIController is HumanAIController &&
!c.IsDead &&
c.SpeechImpediment <= 100.0f);
pendingConversationLines.AddRange(NPCConversation.CreateRandom(availableSpeakers));
}
yield return new WaitForSeconds(1.0f); yield return new WaitForSeconds(1.0f);
@@ -1384,16 +1253,6 @@ namespace Barotrauma
if (GameMain.NetworkMember != null) GameMain.Client.SelectCrewCharacter(character, previewPlayer); if (GameMain.NetworkMember != null) GameMain.Client.SelectCrewCharacter(character, previewPlayer);
private bool ReportButtonClicked(GUIButton button, object userData)
{
//order targeted to all characters
Order order = userData as Order;
if (order.TargetAllCharacters)
{
if (Character.Controlled == null || Character.Controlled.CurrentHull == null) return false;
AddOrder(new Order(order.Prefab, Character.Controlled.CurrentHull, null), order.Prefab.FadeOutTime);
SetCharacterOrder(null, order, "", Character.Controlled);
}
return true; return true;
} }
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -229,7 +230,10 @@ namespace Barotrauma
campaign.lastUpdateID = updateID; campaign.lastUpdateID = updateID;
} }
} }
public override void Save(XElement element)
{
//do nothing, the clients get the save files from the server
}
} }
} }
@@ -141,9 +141,6 @@ namespace Barotrauma
infoFrame?.UpdateManually(deltaTime); infoFrame?.UpdateManually(deltaTime);
} }
infoFrame?.UpdateManually(deltaTime);
}
public void Draw(SpriteBatch spriteBatch) public void Draw(SpriteBatch spriteBatch)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
@@ -47,7 +47,7 @@ namespace Barotrauma
//if "%" is found //if "%" is found
if (index > 0) if (index > 0)
{ {
while (true) while (index > 0)
{ {
//search for end of label //search for end of label
index -= 1; index -= 1;
@@ -439,7 +439,6 @@ namespace Barotrauma
} }
}; };
#endif #endif
var radioButtonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), voiceSettings.RectTransform)) var radioButtonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), voiceSettings.RectTransform))
{ {
Stretch = true, Stretch = true,
@@ -458,6 +457,24 @@ namespace Barotrauma
voiceMode.AddRadioButton((VoiceMode)i, tick); voiceMode.AddRadioButton((VoiceMode)i, tick);
} }
var micVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.18f), voiceSettings.RectTransform), TextManager.Get("MicrophoneVolume"));
var micVolumeSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.18f), voiceSettings.RectTransform),
barSize: 0.1f)
{
UserData = micVolumeText,
BarScroll = (float)Math.Sqrt(MathUtils.InverseLerp(0.2f, 5.0f, MicrophoneVolume)),
OnMoved = (scrollBar, scroll) =>
{
MicrophoneVolume = MathHelper.Lerp(0.2f, 5.0f, scroll * scroll);
MicrophoneVolume = (float)Math.Round(MicrophoneVolume, 1);
ChangeSliderText(scrollBar, MicrophoneVolume);
scrollBar.Step = 0.05f;
return true;
},
Step = 0.05f
};
micVolumeSlider.OnMoved(micVolumeSlider, micVolumeSlider.BarScroll);
var voiceInputContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.2f), voiceSettings.RectTransform, Anchor.BottomCenter)); var voiceInputContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.2f), voiceSettings.RectTransform, Anchor.BottomCenter));
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice") + ": "); new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice") + ": ");
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight),
@@ -469,7 +486,6 @@ namespace Barotrauma
voiceKeyBox.SelectedColor = Color.Gold * 0.3f; voiceKeyBox.SelectedColor = Color.Gold * 0.3f;
var voiceActivityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), voiceSettings.RectTransform)); var voiceActivityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), voiceSettings.RectTransform));
GUITextBlock noiseGateText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), voiceActivityGroup.RectTransform), TextManager.Get("NoiseGateThreshold")) GUITextBlock noiseGateText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), voiceActivityGroup.RectTransform), TextManager.Get("NoiseGateThreshold"))
{ {
TextGetter = () => TextGetter = () =>
@@ -509,13 +525,13 @@ namespace Barotrauma
if (GameMain.Client == null && VoipCapture.Instance == null) if (GameMain.Client == null && VoipCapture.Instance == null)
{ {
VoipCapture.Create(GameMain.Config.VoiceCaptureDevice); VoipCapture.Create(GameMain.Config.VoiceCaptureDevice);
} if (VoipCapture.Instance == null)
if (VoipCapture.Instance == null) {
{ VoiceSetting = vMode = VoiceMode.Disabled;
VoiceSetting = vMode = VoiceMode.Disabled; voiceInputContainer.Visible = false;
voiceInputContainer.Visible = false; voiceActivityGroup.Visible = false;
voiceActivityGroup.Visible = false; return;
return; }
} }
} }
else else
@@ -615,16 +631,6 @@ namespace Barotrauma
.Replace("[missingfiletypes]", string.Join(", ", missingContentTypes)); .Replace("[missingfiletypes]", string.Join(", ", missingContentTypes));
} }
} }
languageDD.SelectItem(TextManager.Language);
languageDD.OnSelected = (guiComponent, obj) =>
{
string newLanguage = obj as string;
if (newLanguage == Language) return true;
UnsavedSettings = true;
Language = newLanguage;
new GUIMessageBox(TextManager.Get("RestartRequiredLabel"), TextManager.Get("RestartRequiredLanguage"));
//spacing //spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), generalLayoutGroup.RectTransform), style: null);
@@ -25,15 +25,6 @@ namespace Barotrauma.Items.Components
public readonly ActionType Type; public readonly ActionType Type;
public string VolumeProperty; public string VolumeProperty;
public float VolumeMultiplier
{
get { return RoundSound.Volume; }
}
public float Range
{
get { return RoundSound.Range; }
}
public float VolumeMultiplier public float VolumeMultiplier
{ {
@@ -198,20 +189,20 @@ namespace Barotrauma.Items.Components
{ {
if (loopingSoundChannel != null) if (loopingSoundChannel != null)
{ {
loopingSoundChannel.Dispose(); loopingSoundChannel = null; loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
} }
return; return;
} }
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound) if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
{ {
loopingSoundChannel.Dispose(); loopingSoundChannel = null; loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
} }
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{ {
loopingSoundChannel = loopingSound.RoundSound.Sound.Play( loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(position.X, position.Y, 0.0f), new Vector3(position.X, position.Y, 0.0f),
GetSoundVolume(loopingSound), 0.01f,
SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull)); SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true; loopingSoundChannel.Looping = true;
//TODO: tweak //TODO: tweak
@@ -226,7 +217,9 @@ namespace Barotrauma.Items.Components
lastMuffleCheckTime = (float)Timing.TotalTime; lastMuffleCheckTime = (float)Timing.TotalTime;
} }
loopingSoundChannel.Muffled = shouldMuffleLooping; loopingSoundChannel.Muffled = shouldMuffleLooping;
loopingSoundChannel.Gain = GetSoundVolume(loopingSound); float targetGain = GetSoundVolume(loopingSound);
float gainDiff = targetGain - loopingSoundChannel.Gain;
loopingSoundChannel.Gain += Math.Abs(gainDiff) < 0.1f ? gainDiff : Math.Sign(gainDiff) * 0.1f;
loopingSoundChannel.Position = new Vector3(position.X, position.Y, 0.0f); loopingSoundChannel.Position = new Vector3(position.X, position.Y, 0.0f);
} }
return; return;
@@ -279,13 +272,13 @@ namespace Barotrauma.Items.Components
loopingSound = itemSound; loopingSound = itemSound;
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound) if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
{ {
loopingSoundChannel.Dispose(); loopingSoundChannel = null; loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
} }
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{ {
loopingSoundChannel = loopingSound.RoundSound.Sound.Play( loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(position.X, position.Y, 0.0f), new Vector3(position.X, position.Y, 0.0f),
GetSoundVolume(loopingSound), 0.01f,
muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull)); muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true; loopingSoundChannel.Looping = true;
//TODO: tweak //TODO: tweak
@@ -309,7 +302,7 @@ namespace Barotrauma.Items.Components
if (loopingSoundChannel != null) if (loopingSoundChannel != null)
{ {
loopingSoundChannel.Dispose(); loopingSoundChannel.FadeOutAndDispose();
loopingSoundChannel = null; loopingSoundChannel = null;
loopingSound = null; loopingSound = null;
} }
@@ -392,12 +392,12 @@ namespace Barotrauma.Items.Components
Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale); Vector2 pos = new Vector2(rect.Center.X, rect.Y + meterSprite.Origin.Y * scale);
Vector2 optimalRangeNormalized = new Vector2( Vector2 optimalRangeNormalized = new Vector2(
(optimalRange.X - range.X) / (range.Y - range.X), MathHelper.Clamp((optimalRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
(optimalRange.Y - range.X) / (range.Y - range.X)); MathHelper.Clamp((optimalRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
Vector2 allowedRangeNormalized = new Vector2( Vector2 allowedRangeNormalized = new Vector2(
(allowedRange.X - range.X) / (range.Y - range.X), MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
(allowedRange.Y - range.X) / (range.Y - range.X)); MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
Vector2 sectorRad = new Vector2(-1.57f, 1.57f); Vector2 sectorRad = new Vector2(-1.57f, 1.57f);
@@ -417,10 +417,10 @@ namespace Barotrauma.Items.Components
{ {
spriteBatch.End(); spriteBatch.End();
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0,0,GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale)); 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); 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.Orange, optimalSectorRad.X, scale);
sectorSprite.Draw(spriteBatch, pos, Color.Red, allowedSectorRad.X, scale); sectorSprite.Draw(spriteBatch, pos, Color.Red, allowedSectorRad.X, scale);
@@ -156,7 +156,7 @@ namespace Barotrauma.Items.Components
{ {
if (moveSound != null) if (moveSound != null)
{ {
moveSoundChannel.Dispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, moveSound.Volume, moveSound.Range, item.WorldPosition); moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, moveSound.Volume, moveSound.Range, item.WorldPosition);
if (moveSoundChannel != null) moveSoundChannel.Looping = true; if (moveSoundChannel != null) moveSoundChannel.Looping = true;
} }
@@ -168,13 +168,13 @@ namespace Barotrauma.Items.Components
{ {
if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound) if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound)
{ {
moveSoundChannel.Dispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, endMoveSound.Volume, endMoveSound.Range, item.WorldPosition); moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, endMoveSound.Volume, endMoveSound.Range, item.WorldPosition);
if (moveSoundChannel != null) moveSoundChannel.Looping = false; if (moveSoundChannel != null) moveSoundChannel.Looping = false;
} }
else if (!moveSoundChannel.IsPlaying) else if (!moveSoundChannel.IsPlaying)
{ {
moveSoundChannel.Dispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = null; moveSoundChannel = null;
} }
@@ -288,7 +288,10 @@ namespace Barotrauma.Items.Components
availableAmmo.AddRange(itemContainer.Inventory.Items); 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 charged = batteryCharge * 3600.0f > powerConsumption;
bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null); bool readyToFire = reload <= 0.0f && charged && availableAmmo.Any(p => p != null);
if (ShowChargeIndicator && PowerConsumption > 0.0f) if (ShowChargeIndicator && PowerConsumption > 0.0f)
@@ -158,7 +158,7 @@ namespace Barotrauma
if (!Visible || (!editing && hiddenInGame)) return; if (!Visible || (!editing && hiddenInGame)) return;
if (editing && !ShowItems) return; if (editing && !ShowItems) return;
Color color = isHighlighted && !GUI.DisableItemHighlights ? Color.Orange : GetSpriteColor(); Color color = isHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? Color.Orange : GetSpriteColor();
//if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f); //if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f);
Sprite activeSprite = prefab.sprite; Sprite activeSprite = prefab.sprite;
@@ -411,7 +411,6 @@ namespace Barotrauma
} }
} }
} }
} }
public override void UpdateEditing(Camera cam) public override void UpdateEditing(Camera cam)
@@ -483,16 +482,43 @@ namespace Barotrauma
linkText.TextColor = Color.Yellow; linkText.TextColor = Color.Yellow;
itemsText.TextColor = Color.Yellow; itemsText.TextColor = Color.Yellow;
} }
if (!inGame && Sprite != null)
if (!inGame)
{ {
var reloadTextureButton = new GUIButton(new RectTransform(new Point(editingHUD.Rect.Width / 2, 20)), TextManager.Get("ReloadSprite")); var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, 20)), isHorizontal: true)
reloadTextureButton.OnClicked += (button, data) =>
{ {
Sprite.ReloadXML(); Stretch = true,
Sprite.ReloadTexture(); RelativeSpacing = 0.02f
return true;
}; };
itemEditor.AddCustomContent(reloadTextureButton, itemEditor.ContentCount); new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
return true;
}
};
if (Sprite != null)
{
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"));
reloadTextureButton.OnClicked += (button, data) =>
{
Sprite.ReloadXML();
Sprite.ReloadTexture();
return true;
};
}
itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount);
} }
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
@@ -599,61 +599,5 @@ 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);
bool hasFireSources = message.ReadBoolean();
int fireSourceCount = 0;
List<Vector3> newFireSources = new List<Vector3>();
if (hasFireSources)
{
fireSourceCount = message.ReadRangedInteger(0, 16);
for (int i = 0; i < fireSourceCount; i++)
{
newFireSources.Add(new Vector3(
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
MathHelper.Clamp(message.ReadRangedSingle(0.0f, 1.0f, 8), 0.05f, 0.95f),
message.ReadRangedSingle(0.0f, 1.0f, 8)));
}
}
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);
}
}
}
} }
} }
@@ -211,7 +211,7 @@ namespace Barotrauma
} }
else if (SoundChannels[i] != null && SoundChannels[i].IsPlaying) else if (SoundChannels[i] != null && SoundChannels[i].IsPlaying)
{ {
SoundChannels[i].Dispose(); SoundChannels[i].FadeOutAndDispose();
SoundChannels[i] = null; SoundChannels[i] = null;
} }
} }
@@ -43,10 +43,18 @@ namespace Barotrauma.Lights
get; get;
private set; private set;
} }
public RenderTarget2D HighlightMap
{
get;
private set;
}
private readonly Texture2D highlightRaster;
private BasicEffect lightEffect; private BasicEffect lightEffect;
public Effect LosEffect { get; private set; } public Effect LosEffect { get; private set; }
public Effect SolidColorEffect { get; private set; }
private List<LightSource> lights; private List<LightSource> lights;
@@ -59,7 +67,6 @@ namespace Barotrauma.Lights
private Texture2D visionCircle; private Texture2D visionCircle;
private Dictionary<Hull, Color> hullAmbientLights; private Dictionary<Hull, Color> hullAmbientLights;
private Dictionary<Hull, Color> smoothedHullAmbientLights; private Dictionary<Hull, Color> smoothedHullAmbientLights;
@@ -73,6 +80,8 @@ namespace Barotrauma.Lights
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png"); visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
GameMain.Instance.OnResolutionChanged += () => GameMain.Instance.OnResolutionChanged += () =>
{ {
@@ -81,8 +90,10 @@ namespace Barotrauma.Lights
#if WINDOWS #if WINDOWS
LosEffect = content.Load<Effect>("Effects/losshader"); LosEffect = content.Load<Effect>("Effects/losshader");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor");
#else #else
LosEffect = content.Load<Effect>("Effects/losshader_opengl"); LosEffect = content.Load<Effect>("Effects/losshader_opengl");
SolidColorEffect = content.Load<Effect>("Effects/solidcolor_opengl");
#endif #endif
if (lightEffect == null) if (lightEffect == null)
@@ -117,6 +128,12 @@ namespace Barotrauma.Lights
pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount, pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
RenderTargetUsage.DiscardContents); RenderTargetUsage.DiscardContents);
HighlightMap?.Dispose();
HighlightMap = new RenderTarget2D(graphics,
(int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale), (int)(GameMain.GraphicsHeight * GameMain.Config.LightMapScale), false,
pp.BackBufferFormat, pp.DepthStencilFormat, pp.MultiSampleCount,
RenderTargetUsage.DiscardContents);
LosTexture?.Dispose(); LosTexture?.Dispose();
LosTexture = new RenderTarget2D(graphics, (int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale), (int)(GameMain.GraphicsHeight * GameMain.Config.LightMapScale), false, SurfaceFormat.Color, DepthFormat.None); LosTexture = new RenderTarget2D(graphics, (int)(GameMain.GraphicsWidth * GameMain.Config.LightMapScale), (int)(GameMain.GraphicsHeight * GameMain.Config.LightMapScale), false, SurfaceFormat.Color, DepthFormat.None);
} }
@@ -187,6 +204,8 @@ namespace Barotrauma.Lights
Matrix transform = cam.ShaderTransform Matrix transform = cam.ShaderTransform
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
bool highlightsVisible = UpdateHighlights(graphics, spriteBatch, spriteBatchTransform, cam);
if (GameMain.Config.SpecularityEnabled) if (GameMain.Config.SpecularityEnabled)
{ {
UpdateSpecularMap(graphics, spriteBatch, spriteBatchTransform, cam, backgroundObstructor); UpdateSpecularMap(graphics, spriteBatch, spriteBatchTransform, cam, backgroundObstructor);
@@ -211,7 +230,6 @@ namespace Barotrauma.Lights
graphics.Clear(Color.Black); graphics.Clear(Color.Black);
graphics.BlendState = BlendState.Additive; graphics.BlendState = BlendState.Additive;
//draw background lights //draw background lights
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
bool backgroundSpritesDrawn = false; bool backgroundSpritesDrawn = false;
@@ -261,30 +279,6 @@ namespace Barotrauma.Lights
//and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites) //and light sprites (done before drawing the actual light volumes so we can make characters obstruct the highlights and sprites)
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
if (Character.Controlled != null)
{
if (Character.Controlled.FocusedItem != null)
{
Character.Controlled.FocusedItem.IsHighlighted = true;
}
if (Character.Controlled.FocusedCharacter != null)
{
Character.Controlled.FocusedCharacter.Draw(spriteBatch, cam);
}
if (!GUI.DisableItemHighlights)
{
foreach (Item item in Item.ItemList)
{
if (item.IsHighlighted)
{
item.Draw(spriteBatch, false, true);
}
}
}
}
foreach (LightSource light in activeLights) foreach (LightSource light in activeLights)
{ {
if (light.IsBackground) continue; if (light.IsBackground) continue;
@@ -292,6 +286,13 @@ namespace Barotrauma.Lights
} }
spriteBatch.End(); spriteBatch.End();
if (highlightsVisible)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
spriteBatch.Draw(HighlightMap, Vector2.Zero, Color.White);
spriteBatch.End();
}
//draw characters to obstruct the highlighted items/characters and light sprites //draw characters to obstruct the highlighted items/characters and light sprites
//--------------------------------------------------------------------------------------------------- //---------------------------------------------------------------------------------------------------
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: spriteBatchTransform);
@@ -377,13 +378,95 @@ namespace Barotrauma.Lights
graphics.BlendState = BlendState.AlphaBlend; graphics.BlendState = BlendState.AlphaBlend;
} }
private readonly List<Entity> highlightedEntities = new List<Entity>();
private bool UpdateHighlights(GraphicsDevice graphics, SpriteBatch spriteBatch, Matrix spriteBatchTransform, Camera cam)
{
if (GUI.DisableItemHighlights) { return false; }
highlightedEntities.Clear();
if (Character.Controlled != null)
{
if (Character.Controlled.FocusedItem != null)
{
highlightedEntities.Add(Character.Controlled.FocusedItem);
}
if (Character.Controlled.FocusedCharacter != null)
{
highlightedEntities.Add(Character.Controlled.FocusedCharacter);
}
foreach (Item item in Item.ItemList)
{
if (item.IsHighlighted && !highlightedEntities.Contains(item))
{
highlightedEntities.Add(item);
}
}
}
if (highlightedEntities.Count == 0) { return false; }
//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)
{
if (highlighted is Item item)
{
item.Draw(spriteBatch, false, true);
}
else if (highlighted is Character character)
{
character.Draw(spriteBatch, cam);
}
}
spriteBatch.End();
//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
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)
{
if (highlighted is Item item)
{
SolidColorEffect.Parameters["blurDistance"].SetValue(0.02f);
item.Draw(spriteBatch, false, true);
}
else if (highlighted is Character character)
{
SolidColorEffect.Parameters["blurDistance"].SetValue(0.05f);
character.Draw(spriteBatch, cam);
}
}
spriteBatch.End();
//raster pattern on top of everything
spriteBatch.Begin(blendState: BlendState.AlphaBlend, samplerState: SamplerState.LinearWrap);
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;
}
public void UpdateSpecularMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Matrix spriteBatchTransform, Camera cam, RenderTarget2D backgroundObstructor = null) public void UpdateSpecularMap(GraphicsDevice graphics, SpriteBatch spriteBatch, Matrix spriteBatchTransform, Camera cam, RenderTarget2D backgroundObstructor = null)
{ {
graphics.SetRenderTarget(SpecularMap); graphics.SetRenderTarget(SpecularMap);
//clear the lightmap //clear the lightmap
graphics.Clear(Color.Black); graphics.Clear(Color.Gray);
graphics.BlendState = BlendState.AlphaBlend; graphics.BlendState = BlendState.AlphaBlend;
spriteBatch.Begin(sortMode: SpriteSortMode.Deferred, blendState: BlendState.AlphaBlend, transformMatrix: spriteBatchTransform); spriteBatch.Begin(sortMode: SpriteSortMode.Deferred, blendState: BlendState.AlphaBlend, transformMatrix: spriteBatchTransform);
@@ -394,20 +477,31 @@ namespace Barotrauma.Lights
} }
spriteBatch.End(); spriteBatch.End();
//TODO: specular maps for level walls
Level.Loaded?.Renderer?.RenderWalls(graphics, cam, specular: true); Level.Loaded?.Renderer?.RenderWalls(graphics, cam, specular: true);
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend); 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());
SolidColorEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, effect: SolidColorEffect);
if (backgroundObstructor != null) if (backgroundObstructor != null)
{ {
spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0, spriteBatch.Draw(backgroundObstructor, new Rectangle(0, 0,
(int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.Black); (int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)), Color.White);
}
foreach (Character c in Character.CharacterList)
{
if (c.Enabled) { c.Draw(spriteBatch, cam); }
} }
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0,
(int)(GameMain.GraphicsWidth * currLightMapScale), (int)(GameMain.GraphicsHeight * currLightMapScale)),
Color.White * 0.4f, isFilled: true);
spriteBatch.End(); spriteBatch.End();
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
graphics.SetRenderTarget(null); graphics.SetRenderTarget(null);
graphics.BlendState = BlendState.AlphaBlend; graphics.BlendState = BlendState.AlphaBlend;
} }
@@ -549,7 +549,6 @@ namespace Barotrauma
if ((PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))) if ((PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl)))
{ {
//TODO: a UI button for flipping entities
if (PlayerInput.KeyHit(Keys.N)) if (PlayerInput.KeyHit(Keys.N))
{ {
float minX = selectedList[0].WorldRect.X, maxX = selectedList[0].WorldRect.Right; float minX = selectedList[0].WorldRect.X, maxX = selectedList[0].WorldRect.Right;
@@ -92,14 +92,40 @@ namespace Barotrauma
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null); GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null);
var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, elementHeight: 20); var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, elementHeight: 20);
var reloadTextureButton = new GUIButton(new RectTransform(new Point(editingHUD.Rect.Width / 2, 20)), TextManager.Get("ReloadSprite"));
reloadTextureButton.OnClicked += (button, data) => var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, 20)), isHorizontal: true)
{ {
Sprite.ReloadXML(); Stretch = true,
Sprite.ReloadTexture(); RelativeSpacing = 0.02f
return true;
}; };
editor.AddCustomContent(reloadTextureButton, editor.ContentCount); new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"))
{
ToolTip = TextManager.Get("MirrorEntityXToolTip"),
OnClicked = (button, data) =>
{
FlipX(relativeToSub: false);
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityY"))
{
ToolTip = TextManager.Get("MirrorEntityYToolTip"),
OnClicked = (button, data) =>
{
FlipY(relativeToSub: false);
return true;
}
};
var reloadTextureButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("ReloadSprite"))
{
OnClicked = (button, data) =>
{
Sprite.ReloadXML();
Sprite.ReloadTexture();
return true;
}
};
editor.AddCustomContent(buttonContainer, editor.ContentCount);
PositionEditingHUD(); PositionEditingHUD();
@@ -405,6 +405,11 @@ namespace Barotrauma
errorMsgs.Add(TextManager.Get("NoBallastTagsWarning")); errorMsgs.Add(TextManager.Get("NoBallastTagsWarning"));
} }
if (Gap.GapList.Any(g => g.linkedTo.Count == 0))
{
errorMsgs.Add(TextManager.Get("NonLinkedGapsWarning"));
}
if (errorMsgs.Any()) if (errorMsgs.Any())
{ {
new GUIMessageBox(TextManager.Get("Warning"), string.Join("\n\n", errorMsgs), 400, 0); new GUIMessageBox(TextManager.Get("Warning"), string.Join("\n\n", errorMsgs), 400, 0);
@@ -321,18 +321,17 @@ namespace Barotrauma.Networking
// Loop until we are approved // Loop until we are approved
//TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly) //TODO: show the name of the server instead of IP when connecting through the server list (more streamer-friendly)
string connectingText = TextManager.Get("ConnectingTo").Replace("[serverip]", serverIP); string connectingText = TextManager.Get("Connecting");
while (!CanStart && !connectCancelled) while (!CanStart && !connectCancelled)
{ {
if (reconnectBox == null) 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 += CancelConnect;
reconnectBox.Buttons[0].OnClicked += reconnectBox.Close; 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) if (DateTime.Now > reqAuthTime)
{ {
@@ -540,6 +539,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
} }
connected = true; connected = true;
chatBox.InputBox.Enabled = true;
if (GameMain.NetLobbyScreen?.TextBox != null) if (GameMain.NetLobbyScreen?.TextBox != null)
{ {
GameMain.NetLobbyScreen.TextBox.Enabled = true; GameMain.NetLobbyScreen.TextBox.Enabled = true;
@@ -832,6 +832,8 @@ namespace Barotrauma.Networking
DisconnectReason disconnectReason = DisconnectReason.Unknown; DisconnectReason disconnectReason = DisconnectReason.Unknown;
if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason); if (splitMsg.Length > 0) Enum.TryParse(splitMsg[0], out disconnectReason);
DebugConsole.NewMessage("Received a disconnect message (" + disconnectMsg + ")");
if (disconnectReason == DisconnectReason.ServerFull) if (disconnectReason == DisconnectReason.ServerFull)
{ {
//already waiting for a slot to free up, do nothing //already waiting for a slot to free up, do nothing
@@ -861,22 +863,19 @@ namespace Barotrauma.Networking
waitInServerQueueBox = null; waitInServerQueueBox = null;
CoroutineManager.StopCoroutines("WaitInServerQueue"); CoroutineManager.StopCoroutines("WaitInServerQueue");
} }
else
{
string msg = "";
if (disconnectReason == DisconnectReason.Unknown)
{
msg = disconnectMsg;
}
else
{
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
if (allowReconnect && disconnectReason == DisconnectReason.Unknown) 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( reconnectBox = new GUIMessageBox(
TextManager.Get("ConnectionLost"), TextManager.Get("ConnectionLost"),
TextManager.Get("ConnectionLostReconnecting"), new string[0]); msg, new string[0]);
connected = false; connected = false;
ConnectToServer(serverIP); ConnectToServer(serverIP);
} }
@@ -885,10 +884,12 @@ namespace Barotrauma.Networking
string msg = ""; string msg = "";
if (disconnectReason == DisconnectReason.Unknown) if (disconnectReason == DisconnectReason.Unknown)
{ {
DebugConsole.NewMessage("Do not attempt reconnect (not allowed).");
msg = disconnectMsg; msg = disconnectMsg;
} }
else else
{ {
DebugConsole.NewMessage("Do not attempt to reconnect (DisconnectReason doesn't allow reconnection).");
msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString()); msg = TextManager.Get("DisconnectReason." + disconnectReason.ToString());
for (int i = 1; i < splitMsg.Length; i++) for (int i = 1; i < splitMsg.Length; i++)
@@ -923,25 +924,6 @@ namespace Barotrauma.Networking
} }
yield return new WaitForSeconds(0.5f); yield return new WaitForSeconds(0.5f);
} }
}
waitInServerQueueBox?.Close();
waitInServerQueueBox = null;
yield return CoroutineStatus.Success;
}
private void ReadAchievement(NetIncomingMessage inc)
{
string achievementIdentifier = inc.ReadString();
SteamAchievementManager.UnlockAchievement(achievementIdentifier);
}
private void ReadPermissions(NetIncomingMessage inc)
{
List<string> permittedConsoleCommands = new List<string>();
byte clientID = inc.ReadByte();
waitInServerQueueBox?.Close(); waitInServerQueueBox?.Close();
waitInServerQueueBox = null; waitInServerQueueBox = null;
@@ -1324,12 +1306,6 @@ namespace Barotrauma.Networking
byte botCount = inc.ReadByte(); byte botCount = inc.ReadByte();
BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal; BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal;
byte botCount = inc.ReadByte();
BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal;
byte botCount = inc.ReadByte();
BotSpawnMode botSpawnMode = inc.ReadBoolean() ? BotSpawnMode.Fill : BotSpawnMode.Normal;
bool autoRestartEnabled = inc.ReadBoolean(); bool autoRestartEnabled = inc.ReadBoolean();
float autoRestartTimer = autoRestartEnabled ? inc.ReadFloat() : 0.0f; float autoRestartTimer = autoRestartEnabled ? inc.ReadFloat() : 0.0f;
@@ -1,4 +1,5 @@
using Lidgren.Network; using Lidgren.Network;
using Microsoft.Xna.Framework;
using OpenTK.Audio.OpenAL; using OpenTK.Audio.OpenAL;
using System; using System;
using System.Linq; using System.Linq;
@@ -27,6 +28,11 @@ namespace Barotrauma.Networking
private set; private set;
} }
public float Gain
{
get { return GameMain.Config?.MicrophoneVolume ?? 1.0f; }
}
public DateTime LastEnqueueAudio; public DateTime LastEnqueueAudio;
public override byte QueueID public override byte QueueID
@@ -69,6 +75,7 @@ namespace Barotrauma.Networking
{ {
if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound")) if (!GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "capturedevicenotfound"))
{ {
GUI.SettingsMenuOpen = false;
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("VoipCaptureDeviceNotFound")) new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("VoipCaptureDeviceNotFound"))
{ {
UserData = "capturedevicenotfound" UserData = "capturedevicenotfound"
@@ -160,7 +167,8 @@ namespace Barotrauma.Networking
double maxAmplitude = 0.0f; double maxAmplitude = 0.0f;
for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++) for (int i = 0; i < VoipConfig.BUFFER_SIZE; i++)
{ {
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)); maxAmplitude = Math.Max(maxAmplitude, Math.Abs(sampleVal));
} }
double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0); double dB = Math.Min(20 * Math.Log10(maxAmplitude), 0.0);
@@ -117,7 +117,6 @@ namespace Barotrauma
GameMain.Instance.OnResolutionChanged += OnResolutionChanged; GameMain.Instance.OnResolutionChanged += OnResolutionChanged;
instance = this; instance = this;
} }
#endregion
public override void Deselect() public override void Deselect()
{ {
@@ -196,6 +195,7 @@ namespace Barotrauma
{ {
ParamsEditor.Instance.EditorBox.AddToGUIUpdateList(); ParamsEditor.Instance.EditorBox.AddToGUIUpdateList();
} }
}
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
@@ -351,91 +351,6 @@ namespace Barotrauma
{ {
CopyLimb(selectedLimb); CopyLimb(selectedLimb);
} }
idToCodeName.TryGetValue(id, out string notes);
LimbXElements.Add(id.ToString(), new XElement("limb",
new XAttribute("id", id),
new XAttribute("name", limbName),
new XAttribute("type", limbType.ToString()),
colliderAttributes,
new XElement("sprite",
new XAttribute("texture", TexturePath),
new XAttribute("sourcerect", $"{rectInputs[0].IntValue}, {rectInputs[1].IntValue}, {width}, {height}")),
new XAttribute("notes", null ?? string.Empty)
));
}
}
UpdateJointCreation();
if (PlayerInput.KeyHit(Keys.Left))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.X--;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Right))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.X++;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Down))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.Y++;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Up))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.Y--;
UpdateSourceRect(limb, newRect);
}
}
UpdateJointCreation();
if (PlayerInput.KeyHit(Keys.Left))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.X--;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Right))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.X++;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Down))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.Y++;
UpdateSourceRect(limb, newRect);
}
}
if (PlayerInput.KeyHit(Keys.Up))
{
foreach (var limb in selectedLimbs)
{
var newRect = limb.ActiveSprite.SourceRect;
newRect.Y--;
UpdateSourceRect(limb, newRect);
} }
} }
UpdateJointCreation(); UpdateJointCreation();
@@ -56,10 +56,6 @@ namespace Barotrauma
postProcessEffect.Parameters["xDistortTexture"].SetValue(distortTexture); postProcessEffect.Parameters["xDistortTexture"].SetValue(distortTexture);
} }
distortTexture = TextureLoader.FromFile("Content/Effects/distortnormals.png");
postProcessEffect.Parameters["xDistortTexture"].SetValue(distortTexture);
}
private void CreateRenderTargets(GraphicsDevice graphics) private void CreateRenderTargets(GraphicsDevice graphics)
{ {
renderTarget?.Dispose(); renderTarget?.Dispose();
@@ -1,15 +1,15 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Barotrauma.Tutorials; using Barotrauma.Tutorials;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using System.Diagnostics; using System.Diagnostics;
using Lidgren.Network; using System.IO;
using System.Linq;
using System.Threading; using System.Threading;
namespace Barotrauma namespace Barotrauma
@@ -34,10 +34,13 @@ namespace Barotrauma
private Tab selectedTab; private Tab selectedTab;
private Sprite backgroundSprite; private Sprite backgroundSprite;
private Sprite backgroundVignette;
#region Creation #region Creation
public MainMenuScreen(GameMain game) public MainMenuScreen(GameMain game)
{ {
backgroundVignette = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight) new GUIImage(new RectTransform(new Vector2(0.35f, 0.2f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.05f, 0.05f), AbsoluteOffset = new Point(-5, -5) }, { RelativeOffset = new Vector2(0.05f, 0.05f), AbsoluteOffset = new Point(-5, -5) },
style: "TitleText") style: "TitleText")
@@ -688,6 +691,11 @@ namespace Barotrauma
blurAmount: 0.0f, blurAmount: 0.0f,
aberrationStrength: 0.0f); aberrationStrength: 0.0f);
} }
spriteBatch.Begin(blendState: BlendState.AlphaBlend);
backgroundVignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f,
new Vector2(GameMain.GraphicsWidth / backgroundVignette.size.X, GameMain.GraphicsHeight / backgroundVignette.size.Y));
spriteBatch.End();
} }
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
@@ -431,7 +431,6 @@ namespace Barotrauma
return true; return true;
} }
}; };
shuttleList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), midInfoColumn.RectTransform), elementCount: 10);
//gamemode ------------------------------------------------------------------ //gamemode ------------------------------------------------------------------
@@ -1050,10 +1049,9 @@ namespace Barotrauma
} }
} }
public void SetPlayYourself(bool playYourself) public bool TogglePlayYourself(GUITickBox tickBox)
{ {
this.playYourself.Selected = playYourself; if (tickBox.Selected)
if (playYourself)
{ {
UpdatePlayerFrame(campaignCharacterInfo, allowEditing: campaignCharacterInfo == null); UpdatePlayerFrame(campaignCharacterInfo, allowEditing: campaignCharacterInfo == null);
} }
@@ -1068,6 +1066,7 @@ namespace Barotrauma
TextManager.Get("PlayingAsSpectator"), TextManager.Get("PlayingAsSpectator"),
textAlignment: Alignment.Center); textAlignment: Alignment.Center);
} }
return false;
} }
public void SetPlayYourself(bool playYourself) public void SetPlayYourself(bool playYourself)
@@ -1097,7 +1096,6 @@ namespace Barotrauma
{ {
return; return;
} }
}
//show the player config menu if spectating is not allowed //show the player config menu if spectating is not allowed
if (!playYourself.Selected && !allowSpectating) if (!playYourself.Selected && !allowSpectating)
@@ -716,7 +716,7 @@ namespace Barotrauma
catch (PingException ex) catch (PingException ex)
{ {
string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message); string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message);
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg);
#if DEBUG #if DEBUG
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
#endif #endif
@@ -1913,8 +1913,6 @@ namespace Barotrauma
CreateUI(); CreateUI();
} }
if (tutorial != null) tutorial.Update((float)deltaTime);
hullVolumeFrame.Visible = MapEntity.SelectedList.Any(s => s is Hull); hullVolumeFrame.Visible = MapEntity.SelectedList.Any(s => s is Hull);
saveAssemblyFrame.Visible = MapEntity.SelectedList.Count > 0; saveAssemblyFrame.Visible = MapEntity.SelectedList.Count > 0;
@@ -26,7 +26,7 @@ namespace Barotrauma.Sounds
if (!stream) if (!stream)
{ {
int bufferSize = (int)reader.TotalSamples*reader.Channels; int bufferSize = (int)reader.TotalSamples * reader.Channels;
float[] floatBuffer = new float[bufferSize]; float[] floatBuffer = new float[bufferSize];
short[] shortBuffer = new short[bufferSize]; short[] shortBuffer = new short[bufferSize];
@@ -82,7 +82,7 @@ namespace Barotrauma.Sounds
{ {
if (!muffleFilters.TryGetValue(sampleRate, out BiQuad filter)) if (!muffleFilters.TryGetValue(sampleRate, out BiQuad filter))
{ {
filter = new LowpassFilter(sampleRate, 400); filter = new LowpassFilter(sampleRate, 800);
muffleFilters.Add(sampleRate, filter); muffleFilters.Add(sampleRate, filter);
} }
filter.Process(buffer); filter.Process(buffer);
@@ -176,7 +176,7 @@ namespace Barotrauma.Sounds
get { return gain; } get { return gain; }
set set
{ {
gain = Math.Max(Math.Min(value,1.0f),0.0f); gain = Math.Max(Math.Min(value, 1.0f), 0.0f);
if (ALSourceIndex < 0) return; if (ALSourceIndex < 0) return;
@@ -433,6 +433,12 @@ namespace Barotrauma.Sounds
} }
} }
public bool FadingOutAndDisposing;
public void FadeOutAndDispose()
{
FadingOutAndDisposing = true;
}
public void Dispose() public void Dispose()
{ {
lock (mutex) lock (mutex)
@@ -15,8 +15,8 @@ namespace Barotrauma.Sounds
public bool Disabled public bool Disabled
{ {
Default = 0, get;
Voice = 1 private set;
} }
private IntPtr alcDevice; private IntPtr alcDevice;
@@ -109,12 +109,6 @@ namespace Barotrauma.Sounds
{ {
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); } get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
} }
public int UniqueLoadedSoundCount
{
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
}
private Dictionary<string, Pair<float, bool>> categoryModifiers;
private Dictionary<string, Pair<float, bool>> categoryModifiers; private Dictionary<string, Pair<float, bool>> categoryModifiers;
@@ -322,7 +316,6 @@ namespace Barotrauma.Sounds
} }
return count; return count;
} }
#endif
public SoundChannel GetChannelFromSound(Sound sound) public SoundChannel GetChannelFromSound(Sound sound)
{ {
@@ -420,15 +413,6 @@ namespace Barotrauma.Sounds
{ {
categoryModifiers[category].Second = muffle; categoryModifiers[category].Second = muffle;
} }
else
{
categoryModifiers[category].Second = muffle;
}
else
{
categoryModifiers[category].Second = muffle;
}
}
for (int i = 0; i < playingChannels.Length; i++) for (int i = 0; i < playingChannels.Length; i++)
{ {
@@ -477,7 +461,8 @@ namespace Barotrauma.Sounds
{ {
for (int j = 0; j < playingChannels[i].Length; j++) for (int j = 0; j < playingChannels[i].Length; j++)
{ {
if (playingChannels[i][j] != null && playingChannels[i][j].IsStream) if (playingChannels[i][j] == null) { continue; }
if (playingChannels[i][j].IsStream)
{ {
if (playingChannels[i][j].IsPlaying) if (playingChannels[i][j].IsPlaying)
{ {
@@ -489,10 +474,18 @@ namespace Barotrauma.Sounds
playingChannels[i][j].Dispose(); playingChannels[i][j].Dispose();
} }
} }
else if (playingChannels[i][j].FadingOutAndDisposing)
{
playingChannels[i][j].Gain -= 0.1f;
if (playingChannels[i][j].Gain <= 0.0f)
{
playingChannels[i][j].Dispose();
}
}
} }
} }
} }
Thread.Sleep(50); //TODO: use a separate thread for network audio? Thread.Sleep(10); //TODO: use a separate thread for network audio?
} }
} }
@@ -215,19 +215,19 @@ namespace Barotrauma
for (int i = 0; i < waterAmbienceChannels.Length; i++) for (int i = 0; i < waterAmbienceChannels.Length; i++)
{ {
if (waterAmbienceChannels[i] == null) continue; if (waterAmbienceChannels[i] == null) continue;
waterAmbienceChannels[i].Dispose(); waterAmbienceChannels[i].FadeOutAndDispose();
waterAmbienceChannels[i] = null; waterAmbienceChannels[i] = null;
} }
for (int i = 0; i < FlowSounds.Count; i++) for (int i = 0; i < FlowSounds.Count; i++)
{ {
if (flowSoundChannels[i] == null) continue; if (flowSoundChannels[i] == null) continue;
flowSoundChannels[i].Dispose(); flowSoundChannels[i].FadeOutAndDispose();
flowSoundChannels[i] = null; flowSoundChannels[i] = null;
} }
for (int i = 0; i < fireSoundChannels.Length; i++) for (int i = 0; i < fireSoundChannels.Length; i++)
{ {
if (fireSoundChannels[i] == null) continue; if (fireSoundChannels[i] == null) continue;
fireSoundChannels[i].Dispose(); fireSoundChannels[i].FadeOutAndDispose();
fireSoundChannels[i] = null; fireSoundChannels[i] = null;
} }
fireVolumeLeft[0] = 0.0f; fireVolumeLeft[1] = 0.0f; fireVolumeLeft[0] = 0.0f; fireVolumeLeft[1] = 0.0f;
@@ -414,7 +414,7 @@ namespace Barotrauma
{ {
if (fireSoundChannels[i] != null) if (fireSoundChannels[i] != null)
{ {
fireSoundChannels[i].Dispose(); fireSoundChannels[i].FadeOutAndDispose();
fireSoundChannels[i] = null; fireSoundChannels[i] = null;
} }
} }
@@ -34,7 +34,7 @@ namespace Barotrauma.Sounds
private static BiQuad[] muffleFilters = new BiQuad[] private static BiQuad[] muffleFilters = new BiQuad[]
{ {
new LowpassFilter(VoipConfig.FREQUENCY, 400) new LowpassFilter(VoipConfig.FREQUENCY, 800)
}; };
private static BiQuad[] radioFilters = new BiQuad[] private static BiQuad[] radioFilters = new BiQuad[]
{ {
@@ -26,6 +26,10 @@ namespace Barotrauma
private int subDivX, subDivY; private int subDivX, subDivY;
private static Effect effect; private static Effect effect;
public static Effect Effect
{
get { return effect; }
}
private Point spritePos; private Point spritePos;
private Point spriteSize; private Point spriteSize;
@@ -279,7 +279,6 @@ namespace Barotrauma
//check if another sprite is using the same texture //check if another sprite is using the same texture
if (!string.IsNullOrEmpty(FilePath)) //file can be empty if the sprite is created directly from a Texture2D instance if (!string.IsNullOrEmpty(FilePath)) //file can be empty if the sprite is created directly from a Texture2D instance
{ {
string normalizedFilePath = Path.GetFullPath(file);
foreach (Sprite s in list) foreach (Sprite s in list)
{ {
if (s.FullPath == FullPath) return; if (s.FullPath == FullPath) return;
@@ -1,4 +1,5 @@
#if DEBUG #if DEBUG
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
@@ -14,10 +15,16 @@ namespace Barotrauma
private const string conversationsPath = "Content/NPCConversations"; private const string conversationsPath = "Content/NPCConversations";
private const string infoTextPath = "Content/Texts"; private const string infoTextPath = "Content/Texts";
private const string xmlHeader = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"; private const string xmlHeader = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
public static void Convert(string language) public static void Convert(string language)
{ {
if (TextManager.Language != "English")
{
DebugConsole.ThrowError("Use the english localization when converting .csv to allow copying values");
return;
}
List<string> conversationFiles = new List<string>(); List<string> conversationFiles = new List<string>();
List<string> infoTextFiles = new List<string>(); List<string> infoTextFiles = new List<string>();
@@ -36,6 +43,11 @@ namespace Barotrauma
for (int i = 0; i < conversationFiles.Count; i++) for (int i = 0; i < conversationFiles.Count; i++)
{ {
List<string> xmlContent = ConvertConversationsToXML(File.ReadAllLines(conversationFiles[i], Encoding.UTF8), language); List<string> xmlContent = ConvertConversationsToXML(File.ReadAllLines(conversationFiles[i], Encoding.UTF8), language);
if (xmlContent == null)
{
DebugConsole.ThrowError("NPCConversation Localization .csv to .xml conversion failed for: " + conversationFiles[i]);
continue;
}
string xmlFileFullPath = $"{conversationsPath}/NPCConversations_{language}_NEW.xml"; string xmlFileFullPath = $"{conversationsPath}/NPCConversations_{language}_NEW.xml";
File.WriteAllLines(xmlFileFullPath, xmlContent); File.WriteAllLines(xmlFileFullPath, xmlContent);
DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath); DebugConsole.NewMessage("Conversation localization .xml file successfully created at: " + xmlFileFullPath);
@@ -44,6 +56,11 @@ namespace Barotrauma
for (int i = 0; i < infoTextFiles.Count; i++) for (int i = 0; i < infoTextFiles.Count; i++)
{ {
List<string> xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[i], Encoding.UTF8), language); List<string> xmlContent = ConvertInfoTextToXML(File.ReadAllLines(infoTextFiles[i], Encoding.UTF8), language);
if (xmlContent == null)
{
DebugConsole.ThrowError("InfoText Localization .csv to .xml conversion failed for: " + infoTextFiles[i]);
continue;
}
string xmlFileFullPath = $"{infoTextPath}/{language}Vanilla_NEW.xml"; string xmlFileFullPath = $"{infoTextPath}/{language}Vanilla_NEW.xml";
File.WriteAllLines(xmlFileFullPath, xmlContent); File.WriteAllLines(xmlFileFullPath, xmlContent);
DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath); DebugConsole.NewMessage("InfoText localization .xml file successfully created at: " + xmlFileFullPath);
@@ -61,9 +78,8 @@ namespace Barotrauma
xmlContent.Add(xmlHeader); xmlContent.Add(xmlHeader);
xmlContent.Add($"<infotexts language=\"{language}\">"); xmlContent.Add($"<infotexts language=\"{language}\">");
xmlContent.Add(string.Empty);
for (int i = 0; i < csvContent.Length; i++) for (int i = 1; i < csvContent.Length; i++) // Start at one to ignore header
{ {
csvContent[i] = csvContent[i].Trim(separator); csvContent[i] = csvContent[i].Trim(separator);
@@ -105,7 +121,35 @@ namespace Barotrauma
xmlContent.Add($"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\">"); xmlContent.Add($"<Conversations identifier=\"vanillaconversations\" Language=\"{language}\">");
xmlContent.Add(string.Empty); xmlContent.Add(string.Empty);
xmlContent.Add("<!-- Personality traits -->");
int traitStart = -1;
for (int i = 0; i < csvContent.Length; i++) for (int i = 0; i < csvContent.Length; i++)
{
if (csvContent[i].StartsWith("Personality"))
{
traitStart = i + 1;
break;
}
}
if (traitStart == -1)
{
DebugConsole.ThrowError("Invalid formatting of NPCConversations, no traits found!");
return null;
}
for (int i = 0; i < NPCPersonalityTrait.List.Count; i++) // Traits
{
string[] split = SplitCSV(csvContent[traitStart + i].Trim(separator));
xmlContent.Add(
$"<PersonalityTrait " +
$"{GetVariable("name", split[1])}" +
$"{GetVariable("alloweddialogtags", string.Join(",", NPCPersonalityTrait.List[i].AllowedDialogTags))}" +
$"{GetVariable("commonness", NPCPersonalityTrait.List[i].Commonness.ToString())}/>");
}
for (int i = traitStart + NPCPersonalityTrait.List.Count; i < csvContent.Length; i++) // Conversations
{ {
string[] split = SplitCSV(csvContent[i]); string[] split = SplitCSV(csvContent[i]);
@@ -172,6 +172,7 @@
<Content Include="Shaders\blurshader.fx" /> <Content Include="Shaders\blurshader.fx" />
<Content Include="Shaders\damageshader.fx" /> <Content Include="Shaders\damageshader.fx" />
<Content Include="Shaders\deformshader.fx" /> <Content Include="Shaders\deformshader.fx" />
<Content Include="Shaders\solidcolor.fx" />
<Content Include="Shaders\losshader.fx" /> <Content Include="Shaders\losshader.fx" />
<Content Include="Shaders\postprocess.fx" /> <Content Include="Shaders\postprocess.fx" />
<Content Include="Shaders\watershader.fx" /> <Content Include="Shaders\watershader.fx" />
@@ -252,6 +253,9 @@
<None Include="Content\Effects\postprocess.xnb"> <None Include="Content\Effects\postprocess.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="Content\Effects\solidcolor.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Content\Effects\watershader.xnb"> <None Include="Content\Effects\watershader.xnb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.8.9.5")] [assembly: AssemblyVersion("0.8.9.6")]
[assembly: AssemblyFileVersion("0.8.9.5")] [assembly: AssemblyFileVersion("0.8.9.6")]
@@ -3,11 +3,14 @@ using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
partial class MultiPlayerCampaign : CampaignMode partial class MultiPlayerCampaign : CampaignMode
{ {
private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
public static void StartNewCampaign(string savePath, string subName, string seed) public static void StartNewCampaign(string savePath, string subName, string seed)
{ {
if (string.IsNullOrWhiteSpace(savePath)) return; if (string.IsNullOrWhiteSpace(savePath)) return;
@@ -202,5 +205,32 @@ namespace Barotrauma
CargoManager.PurchaseItem(pi.ItemPrefab, pi.Quantity); CargoManager.PurchaseItem(pi.ItemPrefab, pi.Quantity);
} }
} }
public override void Save(XElement element)
{
XElement modeElement = new XElement("MultiPlayerCampaign",
new XAttribute("money", Money),
new XAttribute("cheatsenabled", CheatsEnabled));
Map.Save(modeElement);
element.Add(modeElement);
//save character data to a separate file
string characterDataPath = GetCharacterDataSavePath();
XDocument characterDataDoc = new XDocument(new XElement("CharacterData"));
foreach (CharacterCampaignData cd in characterData)
{
characterDataDoc.Root.Add(cd.Save());
}
try
{
characterDataDoc.Save(characterDataPath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving multiplayer campaign characters to \"" + characterDataPath + "\" failed!", e);
}
lastSaveID++;
}
} }
} }
@@ -1897,14 +1897,7 @@ namespace Barotrauma.Networking
public override void AddChatMessage(ChatMessage message) public override void AddChatMessage(ChatMessage message)
{ {
if (string.IsNullOrEmpty(message.Text)) { return; } if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null) Log(message.TextWithSender, ServerLog.MessageType.Chat);
{
Log($"{message.Sender}: {message.Text}", ServerLog.MessageType.Chat);
}
else
{
Log($"{message.Text}", ServerLog.MessageType.Chat);
}
base.AddChatMessage(message); base.AddChatMessage(message);
} }
@@ -2367,6 +2360,8 @@ namespace Barotrauma.Networking
public void SendVoteStatus(List<Client> recipients) public void SendVoteStatus(List<Client> recipients)
{ {
if (!recipients.Any()) { return; }
NetOutgoingMessage msg = server.CreateMessage(); NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY); msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ServerNetObject.VOTE); msg.Write((byte)ServerNetObject.VOTE);
@@ -2419,7 +2414,10 @@ namespace Barotrauma.Networking
recipients.Add(otherClient.Connection); recipients.Add(otherClient.Connection);
} }
} }
server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0); if (recipients.Any())
{
server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
}
serverSettings.SaveClientPermissions(); serverSettings.SaveClientPermissions();
} }
@@ -2453,6 +2451,8 @@ namespace Barotrauma.Networking
public void UpdateCheatsEnabled() public void UpdateCheatsEnabled()
{ {
if (!connectedClients.Any()) { return; }
var msg = server.CreateMessage(); var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED); msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED);
msg.Write(DebugConsole.CheatsEnabled); msg.Write(DebugConsole.CheatsEnabled);
@@ -2794,747 +2794,6 @@ namespace Barotrauma.Networking
server.Shutdown(DisconnectReason.ServerShutdown.ToString()); server.Shutdown(DisconnectReason.ServerShutdown.ToString());
} }
public void SendConsoleMessage(string txt, Client recipient)
{
ChatMessage msg = ChatMessage.Create("", txt, ChatMessageType.Console, null);
SendDirectChatMessage(msg, recipient);
}
public void SendDirectChatMessage(ChatMessage msg, Client recipient)
{
if (recipient == null)
{
string errorMsg = "Attempted to send a chat message to a null client.\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameServer.SendDirectChatMessage:ClientNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
msg.NetStateID = recipient.ChatMsgQueue.Count > 0 ?
(ushort)(recipient.ChatMsgQueue.Last().NetStateID + 1) :
(ushort)(recipient.LastRecvChatMsgID + 1);
recipient.ChatMsgQueue.Add(msg);
recipient.LastChatMsgQueueID = msg.NetStateID;
}
/// <summary>
/// Add the message to the chatbox and pass it to all clients who can receive it
/// </summary>
public void SendChatMessage(string message, ChatMessageType? type = null, Client senderClient = null, Character senderCharacter = null)
{
string senderName = "";
Client targetClient = null;
if (type == null)
{
string command = ChatMessage.GetChatMessageCommand(message, out string tempStr);
switch (command.ToLowerInvariant())
{
case "r":
case "radio":
type = ChatMessageType.Radio;
break;
case "d":
case "dead":
type = ChatMessageType.Dead;
break;
default:
if (command != "")
{
if (command == name.ToLowerInvariant())
{
//a private message to the host
}
else
{
targetClient = connectedClients.Find(c =>
command == c.Name.ToLowerInvariant() ||
(c.Character != null && command == c.Character.Name.ToLowerInvariant()));
if (targetClient == null)
{
if (senderClient != null)
{
var chatMsg = ChatMessage.Create(
"", $"ServerMessage.PlayerNotFound~[player]={command}",
ChatMessageType.Error, null);
chatMsg.NetStateID = senderClient.ChatMsgQueue.Count > 0 ?
(ushort)(senderClient.ChatMsgQueue.Last().NetStateID + 1) :
(ushort)(senderClient.LastRecvChatMsgID + 1);
senderClient.ChatMsgQueue.Add(chatMsg);
senderClient.LastChatMsgQueueID = chatMsg.NetStateID;
}
else
{
AddChatMessage($"ServerMessage.PlayerNotFound~[player]={command}", ChatMessageType.Error);
}
return;
}
}
type = ChatMessageType.Private;
}
else
{
type = ChatMessageType.Default;
}
break;
}
message = tempStr;
}
if (gameStarted)
{
if (senderClient == null)
{
//msg sent by the server
if (senderCharacter == null)
{
senderName = name;
}
else //msg sent by an AI character
{
senderName = senderCharacter.Name;
}
}
else //msg sent by a client
{
senderCharacter = senderClient.Character;
senderName = senderCharacter == null ? senderClient.Name : senderCharacter.Name;
//sender doesn't have a character or the character can't speak -> only ChatMessageType.Dead allowed
if (senderCharacter == null || senderCharacter.IsDead || senderCharacter.SpeechImpediment >= 100.0f)
{
type = ChatMessageType.Dead;
}
else if (type == ChatMessageType.Private)
{
//sender has an alive character, sending private messages not allowed
return;
}
}
}
else
{
if (senderClient == null)
{
//msg sent by the server
if (senderCharacter == null)
{
senderName = name;
}
else //sent by an AI character, not allowed when the game is not running
{
return;
}
}
else //msg sent by a client
{
//game not started -> clients can only send normal and private chatmessages
if (type != ChatMessageType.Private) type = ChatMessageType.Default;
senderName = senderClient.Name;
}
}
//check if the client is allowed to send the message
WifiComponent senderRadio = null;
switch (type)
{
case ChatMessageType.Radio:
case ChatMessageType.Order:
if (senderCharacter == null) return;
//return if senderCharacter doesn't have a working radio
var radio = senderCharacter.Inventory?.Items.FirstOrDefault(i => i != null && i.GetComponent<WifiComponent>() != null);
if (radio == null || !senderCharacter.HasEquippedItem(radio)) return;
senderRadio = radio.GetComponent<WifiComponent>();
if (!senderRadio.CanTransmit()) return;
break;
case ChatMessageType.Dead:
//character still alive and capable of speaking -> dead chat not allowed
if (senderClient != null && senderCharacter != null && !senderCharacter.IsDead && senderCharacter.SpeechImpediment < 100.0f)
{
return;
}
break;
}
if (type == ChatMessageType.Server)
{
senderName = null;
senderCharacter = null;
}
else if (type == ChatMessageType.Radio)
{
//send to chat-linked wifi components
senderRadio.TransmitSignal(0, message, senderRadio.Item, senderCharacter, false);
}
//check which clients can receive the message and apply distance effects
foreach (Client client in ConnectedClients)
{
string modifiedMessage = message;
switch (type)
{
case ChatMessageType.Default:
case ChatMessageType.Radio:
case ChatMessageType.Order:
if (senderCharacter != null &&
client.Character != null && !client.Character.IsDead)
{
modifiedMessage = ChatMessage.ApplyDistanceEffect(message, (ChatMessageType)type, senderCharacter, client.Character);
//too far to hear the msg -> don't send
if (string.IsNullOrWhiteSpace(modifiedMessage)) continue;
}
break;
case ChatMessageType.Dead:
//character still alive -> don't send
if (client != senderClient && client.Character != null && !client.Character.IsDead) continue;
break;
case ChatMessageType.Private:
//private msg sent to someone else than this client -> don't send
if (client != targetClient && client != senderClient) continue;
break;
}
var chatMsg = ChatMessage.Create(
senderName,
modifiedMessage,
(ChatMessageType)type,
senderCharacter);
SendDirectChatMessage(chatMsg, client);
}
if (type.Value != ChatMessageType.MessageBox)
{
string myReceivedMessage = type == ChatMessageType.Server || type == ChatMessageType.Error ? TextManager.GetServerMessage(message) : message;
if (!string.IsNullOrWhiteSpace(myReceivedMessage) &&
(targetClient == null || senderClient == null))
{
AddChatMessage(myReceivedMessage, (ChatMessageType)type, senderName, senderCharacter);
}
}
}
public void SendOrderChatMessage(OrderChatMessage message)
{
if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) return;
ChatMessageType messageType = ChatMessage.CanUseRadio(message.Sender) ? ChatMessageType.Radio : ChatMessageType.Default;
//check which clients can receive the message and apply distance effects
foreach (Client client in ConnectedClients)
{
string modifiedMessage = message.Text;
if (message.Sender != null &&
client.Character != null && !client.Character.IsDead)
{
modifiedMessage = ChatMessage.ApplyDistanceEffect(message.Text, messageType, message.Sender, client.Character);
//too far to hear the msg -> don't send
if (string.IsNullOrWhiteSpace(modifiedMessage)) continue;
}
SendDirectChatMessage(message, client);
}
string myReceivedMessage = message.Text;
if (!string.IsNullOrWhiteSpace(myReceivedMessage))
{
AddChatMessage(new OrderChatMessage(message.Order, message.OrderOption, myReceivedMessage, message.TargetEntity, message.TargetCharacter, message.Sender));
}
}
private void FileTransferChanged(FileSender.FileTransferOut transfer)
{
Client recipient = connectedClients.Find(c => c.Connection == transfer.Connection);
if (transfer.FileType == FileTransferType.CampaignSave &&
(transfer.Status == FileTransferStatus.Sending || transfer.Status == FileTransferStatus.Finished) &&
recipient.LastCampaignSaveSendTime != null)
{
recipient.LastCampaignSaveSendTime.Second = (float)NetTime.Now;
}
}
public void SendCancelTransferMsg(FileSender.FileTransferOut transfer)
{
NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.FILE_TRANSFER);
msg.Write((byte)FileTransferMessageType.Cancel);
msg.Write((byte)transfer.SequenceChannel);
CompressOutgoingMessage(msg);
server.SendMessage(msg, transfer.Connection, NetDeliveryMethod.ReliableOrdered, transfer.SequenceChannel);
}
public void UpdateVoteStatus()
{
if (server.Connections.Count == 0 || connectedClients.Count == 0) return;
Client.UpdateKickVotes(connectedClients);
var clientsToKick = connectedClients.FindAll(c =>
c.Connection != OwnerConnection &&
c.KickVoteCount >= connectedClients.Count * serverSettings.KickVoteRequiredRatio);
foreach (Client c in clientsToKick)
{
SendChatMessage($"ServerMessage.KickedFromServer~[client]={c.Name}", ChatMessageType.Server, null);
KickClient(c, "ServerMessage.KickedByVote");
BanClient(c, "ServerMessage.KickedByVoteAutoBan", duration: TimeSpan.FromSeconds(serverSettings.AutoBanTime));
}
GameMain.NetLobbyScreen.LastUpdateID++;
SendVoteStatus(connectedClients);
if (serverSettings.Voting.AllowEndVoting && EndVoteMax > 0 &&
((float)EndVoteCount / (float)EndVoteMax) >= serverSettings.EndVoteRequiredRatio)
{
Log("Ending round by votes (" + EndVoteCount + "/" + (EndVoteMax - EndVoteCount) + ")", ServerLog.MessageType.ServerMessage);
EndGame();
}
}
public void SendVoteStatus(List<Client> recipients)
{
NetOutgoingMessage msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.UPDATE_LOBBY);
msg.Write((byte)ServerNetObject.VOTE);
serverSettings.Voting.ServerWrite(msg);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
CompressOutgoingMessage(msg);
server.SendMessage(msg, recipients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
}
public void UpdateClientPermissions(Client client)
{
if (client.SteamID > 0)
{
serverSettings.ClientPermissions.RemoveAll(cp => cp.SteamID == client.SteamID);
if (client.Permissions != ClientPermissions.None)
{
serverSettings.ClientPermissions.Add(new ServerSettings.SavedClientPermission(
client.Name,
client.SteamID,
client.Permissions,
client.PermittedConsoleCommands));
}
}
else
{
serverSettings.ClientPermissions.RemoveAll(cp => client.IPMatches(cp.IP));
if (client.Permissions != ClientPermissions.None)
{
serverSettings.ClientPermissions.Add(new ServerSettings.SavedClientPermission(
client.Name,
client.Connection.RemoteEndPoint.Address,
client.Permissions,
client.PermittedConsoleCommands));
}
}
var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.PERMISSIONS);
client.WritePermissions(msg);
CompressOutgoingMessage(msg);
//send the message to the client whose permissions are being modified and the clients who are allowed to modify permissions
List<NetConnection> recipients = new List<NetConnection>() { client.Connection };
foreach (Client otherClient in connectedClients)
{
if (otherClient.HasPermission(ClientPermissions.ManagePermissions) && !recipients.Contains(otherClient.Connection))
{
recipients.Add(otherClient.Connection);
}
}
server.SendMessage(msg, recipients, NetDeliveryMethod.ReliableUnordered, 0);
serverSettings.SaveClientPermissions();
}
public void GiveAchievement(Character character, string achievementIdentifier)
{
achievementIdentifier = achievementIdentifier.ToLowerInvariant();
foreach (Client client in connectedClients)
{
if (client.Character == character)
{
GiveAchievement(client, achievementIdentifier);
return;
}
}
}
public void GiveAchievement(Client client, string achievementIdentifier)
{
if (client.GivenAchievements.Contains(achievementIdentifier)) return;
client.GivenAchievements.Add(achievementIdentifier);
var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.ACHIEVEMENT);
msg.Write(achievementIdentifier);
CompressOutgoingMessage(msg);
server.SendMessage(msg, client.Connection, NetDeliveryMethod.ReliableUnordered);
}
public void UpdateCheatsEnabled()
{
var msg = server.CreateMessage();
msg.Write((byte)ServerPacketHeader.CHEATS_ENABLED);
msg.Write(DebugConsole.CheatsEnabled);
msg.WritePadBits();
CompressOutgoingMessage(msg);
server.SendMessage(msg, connectedClients.Select(c => c.Connection).ToList(), NetDeliveryMethod.ReliableUnordered, 0);
}
public void SetClientCharacter(Client client, Character newCharacter)
{
if (client == null) return;
//the client's previous character is no longer a remote player
if (client.Character != null)
{
client.Character.IsRemotePlayer = false;
client.Character.OwnerClientIP = null;
client.Character.OwnerClientName = null;
}
if (newCharacter == null)
{
if (client.Character != null) //removing control of the current character
{
CreateEntityEvent(client.Character, new object[] { NetEntityEvent.Type.Control, null });
client.Character = null;
}
}
else //taking control of a new character
{
newCharacter.ClientDisconnected = false;
newCharacter.KillDisconnectedTimer = 0.0f;
newCharacter.ResetNetState();
if (client.Character != null)
{
newCharacter.LastNetworkUpdateID = client.Character.LastNetworkUpdateID;
}
newCharacter.OwnerClientIP = client.Connection.RemoteEndPoint.Address.ToString();
newCharacter.OwnerClientName = client.Name;
newCharacter.IsRemotePlayer = true;
newCharacter.Enabled = true;
client.Character = newCharacter;
CreateEntityEvent(newCharacter, new object[] { NetEntityEvent.Type.Control, client });
}
}
private void UpdateCharacterInfo(NetIncomingMessage message, Client sender)
{
sender.SpectateOnly = message.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection);
if (sender.SpectateOnly)
{
return;
}
Gender gender = Gender.Male;
Race race = Race.White;
int headSpriteId = 0;
try
{
gender = (Gender)message.ReadByte();
race = (Race)message.ReadByte();
headSpriteId = message.ReadByte();
}
catch (Exception e)
{
//gender = Gender.Male;
//race = Race.White;
//headSpriteId = 0;
DebugConsole.Log("Received invalid characterinfo from \"" + sender.Name + "\"! { " + e.Message + " }");
}
int hairIndex = message.ReadByte();
int beardIndex = message.ReadByte();
int moustacheIndex = message.ReadByte();
int faceAttachmentIndex = message.ReadByte();
List<JobPrefab> jobPreferences = new List<JobPrefab>();
int count = message.ReadByte();
for (int i = 0; i < Math.Min(count, 3); i++)
{
string jobIdentifier = message.ReadString();
JobPrefab jobPrefab = JobPrefab.List.Find(jp => jp.Identifier == jobIdentifier);
if (jobPrefab != null) jobPreferences.Add(jobPrefab);
}
sender.CharacterInfo = new CharacterInfo(Character.HumanConfigFile, sender.Name);
sender.CharacterInfo.RecreateHead(headSpriteId, race, gender, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
//if the client didn't provide job preferences, we'll use the preferences that are randomly assigned in the Client constructor
Debug.Assert(sender.JobPreferences.Count > 0);
if (jobPreferences.Count > 0)
{
sender.JobPreferences = jobPreferences;
}
}
public void AssignJobs(List<Client> unassigned)
{
unassigned = new List<Client>(unassigned);
Dictionary<JobPrefab, int> assignedClientCount = new Dictionary<JobPrefab, int>();
foreach (JobPrefab jp in JobPrefab.List)
{
assignedClientCount.Add(jp, 0);
}
Character.TeamType teamID = Character.TeamType.None;
if (unassigned.Count > 0) { teamID = unassigned[0].TeamID; }
//if we're playing a multiplayer campaign, check which clients already have a character and a job
//(characters are persistent in campaigns)
if (GameMain.GameSession.GameMode is MultiPlayerCampaign multiplayerCampaign)
{
var campaignAssigned = multiplayerCampaign.GetAssignedJobs(connectedClients);
//remove already assigned clients from unassigned
unassigned.RemoveAll(u => campaignAssigned.ContainsKey(u));
//add up to assigned client count
foreach (KeyValuePair<Client, Job> clientJob in campaignAssigned)
{
assignedClientCount[clientJob.Value.Prefab]++;
clientJob.Key.AssignedJob = clientJob.Value.Prefab;
}
}
//count the clients who already have characters with an assigned job
foreach (Client c in connectedClients)
{
if (c.TeamID != teamID || unassigned.Contains(c)) continue;
if (c.Character?.Info?.Job != null && !c.Character.IsDead)
{
assignedClientCount[c.Character.Info.Job.Prefab]++;
}
}
//if any of the players has chosen a job that is Always Allowed, give them that job
for (int i = unassigned.Count - 1; i >= 0; i--)
{
if (unassigned[i].JobPreferences.Count == 0) continue;
if (!unassigned[i].JobPreferences[0].AllowAlways) continue;
unassigned[i].AssignedJob = unassigned[i].JobPreferences[0];
unassigned.RemoveAt(i);
}
//go throught the jobs whose MinNumber>0 (i.e. at least one crew member has to have the job)
bool unassignedJobsFound = true;
while (unassignedJobsFound && unassigned.Count > 0)
{
unassignedJobsFound = false;
foreach (JobPrefab jobPrefab in JobPrefab.List)
{
if (unassigned.Count == 0) break;
if (jobPrefab.MinNumber < 1 || assignedClientCount[jobPrefab] >= jobPrefab.MinNumber) continue;
//find the client that wants the job the most, or force it to random client if none of them want it
Client assignedClient = FindClientWithJobPreference(unassigned, jobPrefab, true);
assignedClient.AssignedJob = jobPrefab;
assignedClientCount[jobPrefab]++;
unassigned.Remove(assignedClient);
//the job still needs more crew members, set unassignedJobsFound to true to keep the while loop running
if (assignedClientCount[jobPrefab] < jobPrefab.MinNumber) unassignedJobsFound = true;
}
}
//attempt to give the clients a job they have in their job preferences
for (int i = unassigned.Count - 1; i >= 0; i--)
{
foreach (JobPrefab preferredJob in unassigned[i].JobPreferences)
{
//the maximum number of players that can have this job hasn't been reached yet
// -> assign it to the client
if (assignedClientCount[preferredJob] < preferredJob.MaxNumber && unassigned[i].Karma >= preferredJob.MinKarma)
{
unassigned[i].AssignedJob = preferredJob;
assignedClientCount[preferredJob]++;
unassigned.RemoveAt(i);
break;
}
}
}
//give random jobs to rest of the clients
foreach (Client c in unassigned)
{
//find all jobs that are still available
var remainingJobs = JobPrefab.List.FindAll(jp => assignedClientCount[jp] < jp.MaxNumber && c.Karma >= jp.MinKarma);
//all jobs taken, give a random job
if (remainingJobs.Count == 0)
{
DebugConsole.ThrowError("Failed to assign a suitable job for \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
int jobIndex = Rand.Range(0, JobPrefab.List.Count);
int skips = 0;
while (c.Karma < JobPrefab.List[jobIndex].MinKarma)
{
jobIndex++;
skips++;
if (jobIndex >= JobPrefab.List.Count) jobIndex -= JobPrefab.List.Count;
if (skips >= JobPrefab.List.Count) break;
}
c.AssignedJob = JobPrefab.List[jobIndex];
assignedClientCount[c.AssignedJob]++;
}
else //some jobs still left, choose one of them by random
{
c.AssignedJob = remainingJobs[Rand.Range(0, remainingJobs.Count)];
assignedClientCount[c.AssignedJob]++;
}
}
}
public void AssignBotJobs(List<CharacterInfo> bots, Character.TeamType teamID)
{
Dictionary<JobPrefab, int> assignedPlayerCount = new Dictionary<JobPrefab, int>();
foreach (JobPrefab jp in JobPrefab.List)
{
assignedPlayerCount.Add(jp, 0);
}
//count the clients who already have characters with an assigned job
foreach (Client c in connectedClients)
{
if (c.TeamID != teamID) continue;
if (c.Character?.Info?.Job != null && !c.Character.IsDead)
{
assignedPlayerCount[c.Character.Info.Job.Prefab]++;
}
else if (c.CharacterInfo?.Job != null)
{
assignedPlayerCount[c.CharacterInfo?.Job.Prefab]++;
}
}
List<CharacterInfo> unassignedBots = new List<CharacterInfo>(bots);
foreach (CharacterInfo bot in bots)
{
foreach (JobPrefab jobPrefab in JobPrefab.List)
{
if (jobPrefab.MinNumber < 1 || assignedPlayerCount[jobPrefab] >= jobPrefab.MinNumber) continue;
bot.Job = new Job(jobPrefab);
assignedPlayerCount[jobPrefab]++;
unassignedBots.Remove(bot);
break;
}
}
//find a suitable job for the rest of the players
foreach (CharacterInfo c in unassignedBots)
{
//find all jobs that are still available
var remainingJobs = JobPrefab.List.FindAll(jp => assignedPlayerCount[jp] < jp.MaxNumber);
//all jobs taken, give a random job
if (remainingJobs.Count == 0)
{
DebugConsole.ThrowError("Failed to assign a suitable job for bot \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
c.Job = new Job(JobPrefab.List[Rand.Range(0, JobPrefab.List.Count)]);
assignedPlayerCount[c.Job.Prefab]++;
}
else //some jobs still left, choose one of them by random
{
c.Job = new Job(remainingJobs[Rand.Range(0, remainingJobs.Count)]);
assignedPlayerCount[c.Job.Prefab]++;
}
}
}
private Client FindClientWithJobPreference(List<Client> clients, JobPrefab job, bool forceAssign = false)
{
int bestPreference = 0;
Client preferredClient = null;
foreach (Client c in clients)
{
if (c.Karma < job.MinKarma) continue;
int index = c.JobPreferences.IndexOf(job);
if (index == -1) index = 1000;
if (preferredClient == null || index < bestPreference)
{
bestPreference = index;
preferredClient = c;
}
}
//none of the clients wants the job, assign it to random client
if (forceAssign && preferredClient == null)
{
preferredClient = clients[Rand.Int(clients.Count)];
}
return preferredClient;
}
public static void Log(string line, ServerLog.MessageType messageType)
{
if (GameMain.Server == null || !GameMain.Server.ServerSettings.SaveServerLogs) return;
GameMain.Server.ServerSettings.ServerLog.WriteLine(line, messageType);
foreach (Client client in GameMain.Server.ConnectedClients)
{
if (!client.HasPermission(ClientPermissions.ServerLog)) continue;
//use sendername as the message type
GameMain.Server.SendDirectChatMessage(
ChatMessage.Create(messageType.ToString(), line, ChatMessageType.ServerLog, null),
client);
}
}
public override void Disconnect()
{
serverSettings.BanList.Save();
serverSettings.SaveSettings();
SteamManager.CloseServer();
if (registeredToMaster)
{
if (restClient != null)
{
var request = new RestRequest("masterserver2.php", Method.GET);
request.AddParameter("action", "removeserver");
request.AddParameter("serverport", Port);
restClient.Execute(request);
restClient = null;
}
}
if (serverSettings.SaveServerLogs)
{
Log("Shutting down the server...", ServerLog.MessageType.ServerMessage);
serverSettings.ServerLog.Save();
}
GameAnalyticsManager.AddDesignEvent("GameServer:ShutDown");
server.Shutdown("The server has been shut down");
}
void InitUPnP() void InitUPnP()
{ {
server.UPnP.ForwardPort(NetPeerConfiguration.Port, "barotrauma"); server.UPnP.ForwardPort(NetPeerConfiguration.Port, "barotrauma");
@@ -222,7 +222,7 @@ namespace Barotrauma.Networking
+ (c.LastRecvEntityEventID + 1).ToString() + + (c.LastRecvEntityEventID + 1).ToString() +
" (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" + " (created " + (Timing.TotalTime - firstEventToResend.CreateTime).ToString("0.##") + " s ago)" +
" Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.ServerMessage); " Events queued: " + events.Count + ", last sent to all: " + lastSentToAll, ServerLog.MessageType.ServerMessage);
server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesync"); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncOldEvent");
} }
); );
} }
@@ -236,7 +236,7 @@ namespace Barotrauma.Networking
{ {
DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red); DebugConsole.NewMessage(c.Name + " was kicked due to excessive desync (expected removed event " + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", Color.Red);
GameServer.Log("Disconnecting client " + c.Name + " due to excessive desync (expected removed event" + (c.LastRecvEntityEventID + 1).ToString() + ", last available is " + events[0].ID.ToString() + ")", ServerLog.MessageType.ServerMessage); 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"); server.DisconnectClient(c, "", "ServerMessage.ExcessiveDesyncRemovedEvent");
}); });
} }
} }
@@ -260,6 +260,7 @@ namespace Barotrauma.Networking
//a client could potentially spam events with a much higher character state ID //a client could potentially spam events with a much higher character state ID
//than the state of their character and/or stop sending character inputs, //than the state of their character and/or stop sending character inputs,
//so we'll drop some events to make sure no-one blows up our buffer //so we'll drop some events to make sure no-one blows up our buffer
DebugConsole.Log("Excessive amount of events in a client's event buffer. The client may be spamming events or their event IDs might be out of sync. Dropping events...");
bufferedEvents.RemoveRange(0, 256); bufferedEvents.RemoveRange(0, 256);
} }
@@ -494,7 +495,6 @@ namespace Barotrauma.Networking
foreach (Client c in server.ConnectedClients) foreach (Client c in server.ConnectedClients)
{ {
c.PositionUpdateLastSent.Clear();
c.EntityEventLastSent.Clear(); c.EntityEventLastSent.Clear();
c.LastRecvEntityEventID = 0; c.LastRecvEntityEventID = 0;
c.LastSentEntityEventID = 0; c.LastSentEntityEventID = 0;
@@ -73,8 +73,8 @@
<Submarine file="Submarines/Muikku.sub" /> <Submarine file="Submarines/Muikku.sub" />
<Submarine file="Submarines/Bunyip.sub" /> <Submarine file="Submarines/Bunyip.sub" />
<Submarine file="Submarines/Humpback.sub" /> <Submarine file="Submarines/Humpback.sub" />
<Submarine file="Submarines/Dugong.sub" />
<Text file="Content/Texts/EnglishVanilla.xml" /> <Text file="Content/Texts/EnglishVanilla.xml" />
<Text file="Content/Texts/EnglishVanillaLoadingTips.xml" />
<UIStyle file="Content/UI/style.xml"/> <UIStyle file="Content/UI/style.xml"/>
<Afflictions file="Content/Afflictions.xml"/> <Afflictions file="Content/Afflictions.xml"/>
<Structure file="Content/Map/StructurePrefabs.xml" /> <Structure file="Content/Map/StructurePrefabs.xml" />
@@ -1005,9 +1005,36 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\pointlight_falloff.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Lights\pointlight_falloff.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienBackground1.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienBackground2.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienBackground3.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienBackground4.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienBackground5.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienBackground6.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienDecals1.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienRuins.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienRuins.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienWallSet1.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\AlienWallSet2.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Map\bWall_Airlock_A1.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Map\bWall_Airlock_A1.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -1382,9 +1409,6 @@
<Content Include="$(MSBuildThisFileDirectory)Content\splashscreen.mp4"> <Content Include="$(MSBuildThisFileDirectory)Content\splashscreen.mp4">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\EnglishVanillaLoadingTips.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Texts\EnglishVanilla.xml"> <Content Include="$(MSBuildThisFileDirectory)Content\Texts\EnglishVanilla.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -1619,6 +1643,12 @@
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\lightcone.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Lights\lightcone.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\divinghelmetlight.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\divinghelmetvisorlight.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\Lights\penumbra.png"> <Content Include="$(MSBuildThisFileDirectory)Content\Lights\penumbra.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -1852,6 +1882,9 @@
<Content Include="$(MSBuildThisFileDirectory)Content\UI\Health\MedUI_Torso.png"> <Content Include="$(MSBuildThisFileDirectory)Content\UI\Health\MedUI_Torso.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\UI\HighlightRaster.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\UI\IconAtlas.png"> <Content Include="$(MSBuildThisFileDirectory)Content\UI\IconAtlas.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -1873,6 +1906,9 @@
<Content Include="$(MSBuildThisFileDirectory)Content\UI\damageOverlay.png"> <Content Include="$(MSBuildThisFileDirectory)Content\UI\damageOverlay.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
<Content Include="$(MSBuildThisFileDirectory)Content\UI\MainMenuVignette.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="$(MSBuildThisFileDirectory)Content\UI\noise.png"> <Content Include="$(MSBuildThisFileDirectory)Content\UI\noise.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content> </Content>
@@ -3089,6 +3125,9 @@
<None Include="$(MSBuildThisFileDirectory)Content\Items\Door\DoorBreak2.ogg"> <None Include="$(MSBuildThisFileDirectory)Content\Items\Door\DoorBreak2.ogg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\PAX.sub">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\Remora.sub">
</None> </None>
<None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub"> <None Include="$(MSBuildThisFileDirectory)Submarines\RemoraDrone.sub">
@@ -359,7 +359,7 @@ namespace Barotrauma
if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f)) if (Character.Submarine == null && SimPosition.Y < ConvertUnits.ToSimUnits(Character.CharacterHealth.CrushDepth * 0.75f))
{ {
//steer straight up if very deep //steer straight up if very deep
steeringManager.SteeringManual(deltaTime, Vector2.UnitY * speed); steeringManager.SteeringManual(deltaTime, Vector2.UnitY);
return; return;
} }
@@ -652,7 +652,7 @@ namespace Barotrauma
{ {
//wander around randomly and decrease the priority faster if no path is found //wander around randomly and decrease the priority faster if no path is found
if (selectedTargetMemory != null) selectedTargetMemory.Priority -= deltaTime * 10.0f; if (selectedTargetMemory != null) selectedTargetMemory.Priority -= deltaTime * 10.0f;
steeringManager.SteeringWander(speed); steeringManager.SteeringWander();
} }
else if (indoorsSteering.CurrentPath.Finished) else if (indoorsSteering.CurrentPath.Finished)
{ {
@@ -131,23 +131,20 @@ namespace Barotrauma
ignorePlatforms = true; ignorePlatforms = true;
} }
} }
if (Character.IsClimbing && PathSteering.InLadders && PathSteering.IsNextLadderSameAsCurrent)
{
Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
}
} }
Character.AnimController.IgnorePlatforms = ignorePlatforms; Character.AnimController.IgnorePlatforms = ignorePlatforms;
// Suspect that this causes issues when trying to exit from the ladders -> could try to check if the next node is ladder?
//if (Character.IsClimbing)
//{
// Character.AnimController.TargetMovement = new Vector2(0.0f, Math.Sign(Character.AnimController.TargetMovement.Y));
//}
Vector2 targetMovement = AnimController.TargetMovement; Vector2 targetMovement = AnimController.TargetMovement;
if (!Character.AnimController.InWater) if (!Character.AnimController.InWater)
{ {
targetMovement = new Vector2( targetMovement = new Vector2(Character.AnimController.TargetMovement.X, MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
Character.AnimController.TargetMovement.X,
MathHelper.Clamp(Character.AnimController.TargetMovement.Y, -1.0f, 1.0f));
} }
float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed); float maxSpeed = Character.ApplyTemporarySpeedLimits(currentSpeed);
@@ -228,10 +225,6 @@ namespace Barotrauma
} }
} }
private void ReportProblems()
{
if (GameMain.Client != null) return;
protected void ReportProblems() protected void ReportProblems()
{ {
Order newOrder = null; Order newOrder = null;
@@ -252,7 +245,7 @@ namespace Barotrauma
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c.CurrentHull == Character.CurrentHull && !c.IsDead && if (c.CurrentHull == Character.CurrentHull && !c.IsDead &&
(c.AIController is EnemyAIController || c.TeamID != Character.TeamID)) (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"); var orderPrefab = Order.PrefabList.Find(o => o.AITag == "reportintruders");
newOrder = new Order(orderPrefab, Character.CurrentHull, null); newOrder = new Order(orderPrefab, Character.CurrentHull, null);
@@ -297,10 +290,13 @@ namespace Barotrauma
public override void OnAttacked(Character attacker, AttackResult attackResult) public override void OnAttacked(Character attacker, AttackResult attackResult)
{ {
float damage = attackResult.Damage; float damage = attackResult.Damage;
if (damage < 0) { return; } if (damage <= 0) { return; }
if (attacker == null || attacker.IsDead || attacker.Removed) if (attacker == null || attacker.IsDead || attacker.Removed)
{ {
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100; if (objectiveManager.CurrentOrder == null)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
return; return;
} }
if (IsFriendly(attacker)) if (IsFriendly(attacker))
@@ -314,7 +310,10 @@ namespace Barotrauma
if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled) if (!attacker.IsRemotePlayer && Character.Controlled != attacker && attacker.AIController != null && attacker.AIController.Enabled)
{ {
// Don't react to damage done by friendly ai, because we know that it's accidental // Don't react to damage done by friendly ai, because we know that it's accidental
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100; if (objectiveManager.CurrentOrder == null)
{
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
return; return;
} }
float currentVitality = Character.CharacterHealth.Vitality; float currentVitality = Character.CharacterHealth.Vitality;
@@ -322,8 +321,10 @@ namespace Barotrauma
if (dmgPercentage < currentVitality / 10) if (dmgPercentage < currentVitality / 10)
{ {
// Don't react to a minor amount of (accidental) dmg done by friendly characters // Don't react to a minor amount of (accidental) dmg done by friendly characters
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100; if (objectiveManager.CurrentOrder == null)
return; {
objectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
}
} }
if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective) if (ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective)
{ {
@@ -465,7 +466,9 @@ namespace Barotrauma
// Even the smallest fire reduces the safety by 50% // Even the smallest fire reduces the safety by 50%
float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X; float fire = hull.FireSources.Count * 0.5f + hull.FireSources.Sum(fs => fs.DamageRange) / hull.Size.X;
float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1)); float fireFactor = ignoreFire ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(fire, 0, 1));
int enemyCount = Character.CharacterList.Count(e => 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) // The hull safety decreases 90% per enemy up to 100% (TODO: test smaller percentages)
float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1)); float enemyFactor = ignoreEnemies ? 1 : MathHelper.Lerp(1, 0, MathHelper.Clamp(enemyCount * 0.9f, 0, 1));
float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor; float safety = oxygenFactor * waterFactor * fireFactor * enemyFactor;
@@ -44,8 +44,8 @@ namespace Barotrauma
} }
public bool InLadders => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Ladders != null; public bool InLadders => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Ladders != null;
private bool IsNextNodeLadder => currentPath.NextNode != null && currentPath.CurrentNode.Ladders != null; public bool IsNextNodeLadder => currentPath.NextNode != null && currentPath.CurrentNode.Ladders != null;
private bool IsNextLadderSameAsCurrent => IsNextNodeLadder && currentPath.CurrentNode.Ladders == currentPath.NextNode.Ladders; public bool IsNextLadderSameAsCurrent => IsNextNodeLadder && currentPath.CurrentNode.Ladders == currentPath.NextNode.Ladders;
public bool InStairs => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Stairs != null; public bool InStairs => currentPath != null && currentPath.CurrentNode != null && currentPath.CurrentNode.Stairs != null;
@@ -213,7 +213,7 @@ namespace Barotrauma
} }
else if (character.AnimController.InWater) else if (character.AnimController.InWater)
{ {
if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < collider.radius * collider.radius) if (Vector2.DistanceSquared(pos, currentPath.CurrentNode.SimPosition) < MathUtils.Pow(collider.radius * 3, 2))
{ {
currentPath.SkipToNextNode(); currentPath.SkipToNextNode();
} }
@@ -221,11 +221,14 @@ namespace Barotrauma
else else
{ {
Vector2 colliderBottom = character.AnimController.GetColliderBottom(); Vector2 colliderBottom = character.AnimController.GetColliderBottom();
Vector2 colliderSize = collider.GetSize();
// Cannot use the head position, because not all characters have head or it can be below the total height of the character
float characterHeight = colliderSize.Y + character.AnimController.ColliderHeightFromFloor;
float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X); float horizontalDistance = Math.Abs(collider.SimPosition.X - currentPath.CurrentNode.SimPosition.X);
bool isAboveVerticalPosition = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y; bool isAboveFeet = currentPath.CurrentNode.SimPosition.Y > colliderBottom.Y;
bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + 1.5f; bool isNotTooHigh = currentPath.CurrentNode.SimPosition.Y < colliderBottom.Y + characterHeight;
if (horizontalDistance < collider.radius * 2 && isAboveVerticalPosition && isNotTooHigh) if (horizontalDistance < collider.radius * 3 && isAboveFeet && isNotTooHigh)
{ {
currentPath.SkipToNextNode(); currentPath.SkipToNextNode();
} }
@@ -31,6 +31,7 @@ namespace Barotrauma
List<Tuple<string, string, string>> contentPackageFiles = new List<Tuple<string, string, string>>(); List<Tuple<string, string, string>> contentPackageFiles = new List<Tuple<string, string, string>>();
foreach (string filePath in filePaths) foreach (string filePath in filePaths)
{ {
if (Path.GetExtension(filePath) == ".csv") continue; // .csv files are not supported
XDocument doc = XMLExtensions.TryLoadXml(filePath); XDocument doc = XMLExtensions.TryLoadXml(filePath);
if (doc == null || doc.Root == null) continue; if (doc == null || doc.Root == null) continue;
string language = doc.Root.GetAttributeString("Language", "English"); string language = doc.Root.GetAttributeString("Language", "English");
@@ -41,6 +42,7 @@ namespace Barotrauma
List<Tuple<string, string, string>> translationFiles = new List<Tuple<string, string, string>>(); List<Tuple<string, string, string>> translationFiles = new List<Tuple<string, string, string>>();
foreach (string filePath in Directory.GetFiles(Path.Combine("Content", "NPCConversations"))) foreach (string filePath in Directory.GetFiles(Path.Combine("Content", "NPCConversations")))
{ {
if (Path.GetExtension(filePath) == ".csv") continue; // .csv files are not supported
XDocument doc = XMLExtensions.TryLoadXml(filePath); XDocument doc = XMLExtensions.TryLoadXml(filePath);
if (doc == null || doc.Root == null) continue; if (doc == null || doc.Root == null) continue;
string language = doc.Root.GetAttributeString("Language", "English"); string language = doc.Root.GetAttributeString("Language", "English");
@@ -139,6 +139,7 @@ namespace Barotrauma
} }
protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false; protected virtual bool ShouldInterruptSubObjective(AIObjective subObjective) => false;
public virtual void OnSelected() { }
protected abstract void Act(float deltaTime); protected abstract void Act(float deltaTime);
@@ -117,6 +117,17 @@ namespace Barotrauma
return weapon; return weapon;
} }
private void Unequip()
{
if (character.SelectedItems.Contains(Weapon))
{
if (!Weapon.AllowedSlots.Contains(InvSlotType.Any) || !character.Inventory.TryPutItem(Weapon, character, new List<InvSlotType>() { InvSlotType.Any }))
{
Weapon.Drop(character);
}
}
}
private bool Equip(float deltaTime) private bool Equip(float deltaTime)
{ {
if (!character.SelectedItems.Contains(Weapon)) if (!character.SelectedItems.Contains(Weapon))
@@ -241,7 +252,19 @@ namespace Barotrauma
HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100; HumanAIController.ObjectiveManager.GetObjective<AIObjectiveFindSafety>().Priority = 100;
} }
public override bool IsCompleted() => Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0.0f; public override bool IsCompleted()
{
bool completed = Enemy == null || Enemy.Removed || Enemy.IsDead || coolDownTimer <= 0;
if (completed)
{
if (Weapon != null)
{
Unequip();
}
}
return completed;
}
public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted); public override bool CanBeCompleted => !abandon && (reloadWeaponObjective == null || reloadWeaponObjective.CanBeCompleted) && (retreatObjective == null || retreatObjective.CanBeCompleted);
public override float GetPriority(AIObjectiveManager objectiveManager) => Enemy == null || Enemy.Removed || Enemy.IsDead ? 0 : 100; public override float GetPriority(AIObjectiveManager objectiveManager) => Enemy == null || Enemy.Removed || Enemy.IsDead ? 0 : 100;
@@ -18,7 +18,7 @@ namespace Barotrauma
private bool isCompleted; private bool isCompleted;
public bool IgnoreAlreadyContainedItems; public string[] ignoredContainerIdentifiers;
public Func<Item, float> GetItemPriority; public Func<Item, float> GetItemPriority;
@@ -95,7 +95,7 @@ namespace Barotrauma
getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers) getItemObjective = new AIObjectiveGetItem(character, itemIdentifiers)
{ {
GetItemPriority = GetItemPriority, GetItemPriority = GetItemPriority,
IgnoreContainedItems = IgnoreAlreadyContainedItems ignoredContainerIdentifiers = ignoredContainerIdentifiers
}; };
AddSubObjective(getItemObjective); AddSubObjective(getItemObjective);
return; return;
@@ -78,16 +78,18 @@ namespace Barotrauma
foreach (FireSource fs in targetHull.FireSources) foreach (FireSource fs in targetHull.FireSources)
{ {
// TODO: check if in the same room?
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range)); bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
if (!character.IsClimbing && (inRange || useExtinquisherTimer > 0.0f)) if (targetHull == character.CurrentHull && (inRange || useExtinquisherTimer > 0.0f))
{ {
useExtinquisherTimer += deltaTime; useExtinquisherTimer += deltaTime;
if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f; if (useExtinquisherTimer > 2.0f) useExtinquisherTimer = 0.0f;
character.CursorPosition = fs.Position; character.CursorPosition = fs.Position;
character.SetInput(InputType.Aim, false, true); character.SetInput(InputType.Aim, false, true);
character.AIController.SteeringManager.Reset(); if (!character.IsClimbing)
{
character.AIController.SteeringManager.Reset();
}
extinguisher.Use(deltaTime, character); extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs)) if (!targetHull.FireSources.Contains(fs))
@@ -1,5 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using System; using Microsoft.Xna.Framework;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -10,31 +10,26 @@ namespace Barotrauma
public override bool ForceRun => true; public override bool ForceRun => true;
private AIObjective subObjective; private AIObjective subObjective;
private string gearTag; private string gearTag;
public override bool IsCompleted() public override bool IsCompleted()
{ {
for (int i = 0; i < character.Inventory.Items.Length; i++) for (int i = 0; i < character.Inventory.Items.Length; i++)
{ {
if (character.Inventory.SlotTypes[i] == InvSlotType.Any || character.Inventory.Items[i] == null) continue; if (character.Inventory.SlotTypes[i] == InvSlotType.Any || character.Inventory.Items[i] == null) { continue; }
if (character.Inventory.Items[i].HasTag(gearTag)) if (character.Inventory.Items[i].HasTag(gearTag))
{ {
var containedItems = character.Inventory.Items[i].ContainedItems; var containedItems = character.Inventory.Items[i].ContainedItems;
if (containedItems == null) continue; if (containedItems == null) { continue; }
var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f); var oxygenTank = containedItems.FirstOrDefault(it => (it.Prefab.Identifier == "oxygentank" || it.HasTag("oxygensource")) && it.Condition > 0.0f);
if (oxygenTank != null) return true; if (oxygenTank != null) { return true; }
} }
} }
return false; return false;
} }
public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted; public AIObjectiveFindDivingGear(Character character, bool needDivingSuit) : base(character, "")
public AIObjectiveFindDivingGear(Character character, bool needDivingSuit)
: base(character, "")
{ {
gearTag = needDivingSuit ? "divingsuit" : "diving"; gearTag = needDivingSuit ? "divingsuit" : "diving";
} }
@@ -54,12 +49,11 @@ namespace Barotrauma
else else
{ {
var containedItems = item.ContainedItems; var containedItems = item.ContainedItems;
if (containedItems == null) return; if (containedItems == null) { return; }
//check if there's an oxygen tank in the mask/suit //check if there's an oxygen tank in the mask/suit
foreach (Item containedItem in containedItems) foreach (Item containedItem in containedItems)
{ {
if (containedItem == null) continue; if (containedItem == null) { continue; }
if (containedItem.Condition <= 0.0f) if (containedItem.Condition <= 0.0f)
{ {
containedItem.Drop(character); containedItem.Drop(character);
@@ -70,35 +64,20 @@ namespace Barotrauma
return; return;
} }
} }
if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted()) if (!(subObjective is AIObjectiveContainItem) || subObjective.IsCompleted())
{ {
character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f); character.Speak(TextManager.Get("DialogGetOxygenTank"), null, 0, "getoxygentank", 30.0f);
subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>()); subObjective = new AIObjectiveContainItem(character, new string[] { "oxygentank", "oxygensource" }, item.GetComponent<ItemContainer>());
} }
} }
if (subObjective != null) if (subObjective != null)
{ {
subObjective.TryComplete(deltaTime); subObjective.TryComplete(deltaTime);
} }
} }
public override float GetPriority(AIObjectiveManager objectiveManager) public override bool CanBeCompleted => subObjective == null || subObjective.CanBeCompleted;
{ public override float GetPriority(AIObjectiveManager objectiveManager) => MathHelper.Clamp(100 - character.OxygenAvailable, 0, 100);
if (character.AnimController.CurrentHull == null) return 100.0f; public override bool IsDuplicate(AIObjective otherObjective) => otherObjective is AIObjectiveFindDivingGear;
if (objectiveManager.CurrentOrder == this)
{
return AIObjectiveManager.OrderPriority;
}
return 100.0f - character.Oxygen;
}
public override bool IsDuplicate(AIObjective otherObjective)
{
return otherObjective is AIObjectiveFindDivingGear;
}
} }
} }
@@ -2,7 +2,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
@@ -25,7 +24,7 @@ namespace Barotrauma
private float searchHullTimer; private float searchHullTimer;
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private AIObjective divingGearObjective; private AIObjectiveFindDivingGear divingGearObjective;
public AIObjectiveFindSafety(Character character) : base(character, "") { } public AIObjectiveFindSafety(Character character) : base(character, "") { }
@@ -35,13 +34,12 @@ namespace Barotrauma
protected override void Act(float deltaTime) protected override void Act(float deltaTime)
{ {
var currentHull = character.AnimController.CurrentHull; var currentHull = character.AnimController.CurrentHull;
if (HumanAIController.NeedsDivingGear(currentHull)) if (HumanAIController.NeedsDivingGear(currentHull) && divingGearObjective == null)
{ {
bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90; bool needsDivingSuit = currentHull == null || currentHull.WaterPercentage > 90;
bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character); bool hasEquipment = needsDivingSuit ? HumanAIController.HasDivingSuit(character) : HumanAIController.HasDivingGear(character);
if ((divingGearObjective == null || !divingGearObjective.CanBeCompleted) && !hasEquipment) if (!hasEquipment)
{ {
// If the previous objective cannot be completed, create a new and try again.
divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit); divingGearObjective = new AIObjectiveFindDivingGear(character, needsDivingSuit);
} }
} }
@@ -55,9 +53,15 @@ namespace Barotrauma
} }
else if (divingGearObjective.CanBeCompleted) else if (divingGearObjective.CanBeCompleted)
{ {
// If diving gear objective is active, wait for it to complete. // If diving gear objective is active and can be completed, wait for it to complete.
return; return;
} }
else
{
divingGearObjective = null;
// Reset the timer so that we get a safe hull target.
searchHullTimer = 0;
}
} }
if (unreachableClearTimer > 0) if (unreachableClearTimer > 0)
@@ -83,17 +87,18 @@ namespace Barotrauma
{ {
if (goToObjective.Target != bestHull) if (goToObjective.Target != bestHull)
{ {
goToObjective = new AIObjectiveGoTo(bestHull, character) // If we need diving gear, we should already have it, if possible.
goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
{ {
AllowGoingOutside = true AllowGoingOutside = HumanAIController.HasDivingSuit(character)
}; };
} }
} }
else else
{ {
goToObjective = new AIObjectiveGoTo(bestHull, character) goToObjective = new AIObjectiveGoTo(bestHull, character, getDivingGearIfNeeded: false)
{ {
AllowGoingOutside = true AllowGoingOutside = HumanAIController.HasDivingSuit(character)
}; };
} }
} }
@@ -110,6 +115,7 @@ namespace Barotrauma
unreachable.Add(goToObjective.Target as Hull); unreachable.Add(goToObjective.Target as Hull);
} }
goToObjective = null; goToObjective = null;
SteeringManager.SteeringWander();
} }
} }
else if (currentHull != null) else if (currentHull != null)
@@ -126,6 +132,11 @@ namespace Barotrauma
foreach (Character enemy in Character.CharacterList) foreach (Character enemy in Character.CharacterList)
{ {
//don't run from friendly NPCs
if (enemy.TeamID == Character.TeamType.FriendlyNPC) { continue; }
//friendly NPCs don't run away from anything but characters controlled by EnemyAIController (= monsters)
if (character.TeamID == Character.TeamType.FriendlyNPC && !(enemy.AIController is EnemyAIController)) { continue; }
if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious && if (enemy.CurrentHull == currentHull && !enemy.IsDead && !enemy.IsUnconscious &&
(enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID)) (enemy.AIController is EnemyAIController || enemy.TeamID != character.TeamID))
{ {
@@ -170,13 +181,15 @@ namespace Barotrauma
if (unreachable.Contains(hull)) { continue; } if (unreachable.Contains(hull)) { continue; }
if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; } if (!character.Submarine.IsConnectedTo(hull.Submarine)) { continue; }
hullSafety = HumanAIController.GetHullSafety(hull, character); hullSafety = HumanAIController.GetHullSafety(hull, character);
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally) // Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally)
float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f; float dist = Math.Abs(character.WorldPosition.X - hull.WorldPosition.X) + Math.Abs(character.WorldPosition.Y - hull.WorldPosition.Y) * 2.0f;
float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist)); float distanceFactor = MathHelper.Lerp(1, 0.9f, MathUtils.InverseLerp(0, 10000, dist));
hullSafety *= distanceFactor; hullSafety *= distanceFactor;
// Each unsafe node reduces the hull safety value. // Each unsafe node reduces the hull safety value.
// Ignore current hull, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Unreachable) { continue; }
int unsafeNodes = path.Nodes.Count(n => n.CurrentHull != character.CurrentHull && HumanAIController.UnsafeHulls.Contains(n.CurrentHull));
hullSafety /= 1 + unsafeNodes; hullSafety /= 1 + unsafeNodes;
// If the target is not inside a friendly submarine, considerably reduce the hull safety. // If the target is not inside a friendly submarine, considerably reduce the hull safety.
if (!character.Submarine.IsEntityFoundOnThisSub(hull, true)) if (!character.Submarine.IsEntityFoundOnThisSub(hull, true))
@@ -31,7 +31,7 @@ namespace Barotrauma
public override bool IsCompleted() public override bool IsCompleted()
{ {
return leak.Open <= 0.0f || leak.Removed || pathUnreachable; return leak.Open <= 0.0f || leak.Removed;
} }
public override bool CanBeCompleted => !abandon && base.CanBeCompleted; public override bool CanBeCompleted => !abandon && base.CanBeCompleted;
@@ -94,16 +94,15 @@ namespace Barotrauma
if (repairTool == null) { return; } if (repairTool == null) { return; }
Vector2 gapDiff = leak.WorldPosition - character.WorldPosition; Vector2 gapDiff = leak.WorldPosition - character.WorldPosition;
var humanoidController = character.AnimController as HumanoidAnimController;
// TODO: use the collider size/reach? // TODO: use the collider size/reach?
if (!character.AnimController.InWater && humanoidController != null && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150) if (!character.AnimController.InWater && Math.Abs(gapDiff.X) < 100 && gapDiff.Y < 0.0f && gapDiff.Y > -150)
{ {
((HumanoidAnimController)character.AnimController).Crouching = true; HumanAIController.AnimController.Crouching = true;
} }
float armLength = humanoidController != null ? ConvertUnits.ToDisplayUnits(humanoidController.ArmLength) : 100; float reach = HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range);
bool cannotReach = gapDiff.Length() > armLength + repairTool.Range; bool cannotReach = ConvertUnits.ToSimUnits(gapDiff.Length()) > reach;
if (cannotReach) if (cannotReach)
{ {
if (gotoObjective != null) if (gotoObjective != null)
@@ -116,7 +115,10 @@ namespace Barotrauma
} }
else else
{ {
gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character); gotoObjective = new AIObjectiveGoTo(ConvertUnits.ToSimUnits(GetStandPosition()), character)
{
CloseEnough = reach * 0.75f
};
if (!subObjectives.Contains(gotoObjective)) if (!subObjectives.Contains(gotoObjective))
{ {
AddSubObjective(gotoObjective); AddSubObjective(gotoObjective);
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
{ {
@@ -14,19 +15,15 @@ namespace Barotrauma
//can be either tags or identifiers //can be either tags or identifiers
private string[] itemIdentifiers; private string[] itemIdentifiers;
private Item targetItem, moveToTarget; private Item targetItem, moveToTarget;
private int currSearchIndex; private int currSearchIndex;
public string[] ignoredContainerIdentifiers;
public bool IgnoreContainedItems;
private AIObjectiveGoTo goToObjective; private AIObjectiveGoTo goToObjective;
private float currItemPriority; private float currItemPriority;
private bool equip; private bool equip;
private HashSet<Item> ignoredItems = new HashSet<Item>();
private bool canBeCompleted = true; private bool canBeCompleted = true;
public override bool CanBeCompleted => canBeCompleted; public override bool CanBeCompleted => canBeCompleted;
@@ -36,25 +33,19 @@ namespace Barotrauma
{ {
return AIObjectiveManager.OrderPriority; return AIObjectiveManager.OrderPriority;
} }
return 1.0f; return 1.0f;
} }
public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) public AIObjectiveGetItem(Character character, Item targetItem, bool equip = false) : base(character, "")
: base(character, "")
{ {
currSearchIndex = -1; currSearchIndex = -1;
this.equip = equip; this.equip = equip;
this.targetItem = targetItem; this.targetItem = targetItem;
} }
public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) public AIObjectiveGetItem(Character character, string itemIdentifier, bool equip = false) : this(character, new string[] { itemIdentifier }, equip) { }
: this(character, new string[] { itemIdentifier }, equip)
{
}
public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) public AIObjectiveGetItem(Character character, string[] itemIdentifiers, bool equip = false) : base(character, "")
: base(character, "")
{ {
currSearchIndex = -1; currSearchIndex = -1;
this.equip = equip; this.equip = equip;
@@ -108,12 +99,12 @@ namespace Barotrauma
FindTargetItem(); FindTargetItem();
if (targetItem == null || moveToTarget == null) if (targetItem == null || moveToTarget == null)
{ {
// TODO: cannot be completed? SteeringManager.SteeringWander();
character?.AIController?.SteeringManager?.Reset();
return; return;
} }
if (Vector2.Distance(character.Position, moveToTarget.Position) < targetItem.InteractDistance * 2.0f) if (moveToTarget.CurrentHull == character.CurrentHull &&
Vector2.DistanceSquared(character.Position, moveToTarget.Position) < MathUtils.Pow(targetItem.InteractDistance * 2, 2))
{ {
int targetSlot = -1; int targetSlot = -1;
if (equip) if (equip)
@@ -169,9 +160,13 @@ namespace Barotrauma
} }
goToObjective.TryComplete(deltaTime); goToObjective.TryComplete(deltaTime);
if (!goToObjective.CanBeCompleted) targetItem = null; if (!goToObjective.CanBeCompleted)
{
targetItem = null;
moveToTarget = null;
ignoredItems.Add(targetItem);
}
} }
} }
/// <summary> /// <summary>
@@ -196,22 +191,30 @@ namespace Barotrauma
currSearchIndex++; currSearchIndex++;
var item = Item.ItemList[currSearchIndex]; var item = Item.ItemList[currSearchIndex];
if (ignoredItems.Contains(item)) { continue; }
if (item.Submarine == null) { continue; }
else if (item.Submarine.TeamID != character.TeamID) { continue; }
else if (character.Submarine != null && !character.Submarine.IsEntityFoundOnThisSub(item, true)) { continue; }
if (item.CurrentHull == null || item.Condition <= 0.0f) continue; if (item.CurrentHull == null || item.Condition <= 0.0f) { continue; }
if (IgnoreContainedItems && item.Container != null) continue; if (itemIdentifiers.None(id => item.Prefab.Identifier == id || item.HasTag(id))) { continue; }
if (!itemIdentifiers.Any(id => item.Prefab.Identifier == id || item.HasTag(id))) continue;
if (ignoredContainerIdentifiers != null && item.Container != null)
{
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
}
//if the item is inside a character's inventory, don't steal it unless the character is dead //if the item is inside a character's inventory, don't steal it unless the character is dead
if (item.ParentInventory is CharacterInventory) if (item.ParentInventory is CharacterInventory)
{ {
if (item.ParentInventory.Owner is Character owner && !owner.IsDead) continue; if (item.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
} }
//if the item is inside an item, which is inside a character's inventory, don't steal it //if the item is inside an item, which is inside a character's inventory, don't steal it
Item rootContainer = item.GetRootContainer(); Item rootContainer = item.GetRootContainer();
if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory) if (rootContainer != null && rootContainer.ParentInventory is CharacterInventory)
{ {
if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) continue; if (rootContainer.ParentInventory.Owner is Character owner && !owner.IsDead) { continue; }
} }
float itemPriority = 0.0f; float itemPriority = 0.0f;
@@ -219,13 +222,13 @@ namespace Barotrauma
{ {
//ignore if the item has zero priority //ignore if the item has zero priority
itemPriority = GetItemPriority(item); itemPriority = GetItemPriority(item);
if (itemPriority <= 0.0f) continue; if (itemPriority <= 0.0f) { continue; }
} }
itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f; itemPriority = itemPriority - Vector2.Distance((rootContainer ?? item).Position, character.Position) * 0.01f;
//ignore if the item has a lower priority than the currently selected one //ignore if the item has a lower priority than the currently selected one
if (moveToTarget != null && itemPriority < currItemPriority) continue; if (moveToTarget != null && itemPriority < currItemPriority) { continue; }
currItemPriority = itemPriority; currItemPriority = itemPriority;
@@ -186,11 +186,11 @@ namespace Barotrauma
bool completed = false; bool completed = false;
float allowedDistance = 0.5f; float allowedDistance = CloseEnough;
if (Target is Item item) if (Target is Item item)
{ {
allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.InteractDistance), allowedDistance); allowedDistance = Math.Max(ConvertUnits.ToSimUnits(item.InteractDistance), CloseEnough);
if (item.IsInsideTrigger(character.WorldPosition)) completed = true; if (item.IsInsideTrigger(character.WorldPosition)) completed = true;
} }
else if (Target is Character targetCharacter) else if (Target is Character targetCharacter)
@@ -200,6 +200,7 @@ namespace Barotrauma
// Check that there is no unsafe or forbidden hulls on the way to the target // Check that there is no unsafe or forbidden hulls on the way to the target
// Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull. // Only do this when the current hull is ok, because otherwise the would block all paths from the current hull to the target hull.
var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition); var path = PathSteering.PathFinder.FindPath(character.SimPosition, hull.SimPosition);
if (path.Unreachable) { continue; }
if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; } if (path.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull) || IsForbidden(n.CurrentHull))) { continue; }
} }
@@ -30,9 +30,7 @@ namespace Barotrauma
base.Update(objectiveManager, deltaTime); base.Update(objectiveManager, deltaTime);
if (ignoreListTimer > ignoreListClearInterval) if (ignoreListTimer > ignoreListClearInterval)
{ {
ignoreList.Clear(); Reset();
ignoreListTimer = 0;
UpdateTargets();
} }
else else
{ {
@@ -67,16 +65,24 @@ namespace Barotrauma
} }
} }
private void Reset()
{
ignoreList.Clear();
ignoreListTimer = 0;
UpdateTargets();
}
public override void OnSelected()
{
Reset();
}
public override float GetPriority(AIObjectiveManager objectiveManager) public override float GetPriority(AIObjectiveManager objectiveManager)
{ {
if (character.Submarine == null) { return 0; } if (character.Submarine == null) { return 0; }
if (targets.None()) { return 0; } if (targets.None()) { return 0; }
float avg = targets.Average(t => Average(t)); float avg = targets.Average(t => Average(t));
if (objectiveManager.CurrentOrder == this) return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority + 20, avg / 100);
{
return AIObjectiveManager.OrderPriority - MathHelper.Max(0, AIObjectiveManager.OrderPriority - avg);
}
return MathHelper.Lerp(0, AIObjectiveManager.OrderPriority, avg / 100);
} }
protected void UpdateTargets() protected void UpdateTargets()
@@ -67,6 +67,7 @@ namespace Barotrauma
private AIObjective GetCurrentObjective() private AIObjective GetCurrentObjective()
{ {
var previousObjective = CurrentObjective;
var firstObjective = Objectives.FirstOrDefault(); var firstObjective = Objectives.FirstOrDefault();
if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this)) if (CurrentOrder != null && firstObjective != null && CurrentOrder.GetPriority(this) > firstObjective.GetPriority(this))
{ {
@@ -76,6 +77,10 @@ namespace Barotrauma
{ {
CurrentObjective = firstObjective; CurrentObjective = firstObjective;
} }
if (previousObjective != CurrentObjective)
{
CurrentObjective?.OnSelected();
}
return CurrentObjective; return CurrentObjective;
} }
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using System; using System;
using System.Linq; using System.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using FarseerPhysics;
namespace Barotrauma namespace Barotrauma
{ {
@@ -84,7 +85,14 @@ namespace Barotrauma
} }
if (character.CanInteractWith(Item)) if (character.CanInteractWith(Item))
{ {
OperateRepairTool(deltaTime); if (repairTool == null)
{
FindRepairTool();
}
if (repairTool != null)
{
OperateRepairTool(deltaTime);
}
foreach (Repairable repairable in Item.Repairables) foreach (Repairable repairable in Item.Repairables)
{ {
if (repairable.CurrentFixer != null && repairable.CurrentFixer != character) if (repairable.CurrentFixer != null && repairable.CurrentFixer != character)
@@ -121,13 +129,17 @@ namespace Barotrauma
subObjectives.Remove(goToObjective); subObjectives.Remove(goToObjective);
} }
goToObjective = new AIObjectiveGoTo(Item, character); goToObjective = new AIObjectiveGoTo(Item, character);
if (repairTool != null)
{
goToObjective.CloseEnough = (HumanAIController.AnimController.ArmLength + ConvertUnits.ToSimUnits(repairTool.Range)) * 0.75f;
}
AddSubObjective(goToObjective); AddSubObjective(goToObjective);
} }
} }
private void OperateRepairTool(float deltaTime) private RepairTool repairTool;
private void FindRepairTool()
{ {
// Operate repair tool, if required.
foreach (Repairable repairable in Item.Repairables) foreach (Repairable repairable in Item.Repairables)
{ {
foreach (var kvp in repairable.requiredItems) foreach (var kvp in repairable.requiredItems)
@@ -138,19 +150,29 @@ namespace Barotrauma
{ {
if (requiredItem.MatchesItem(item)) if (requiredItem.MatchesItem(item))
{ {
var repairTool = item.GetComponent<RepairTool>(); repairTool = item.GetComponent<RepairTool>();
if (repairTool != null)
{
character.CursorPosition = Item.Position;
character.SetInput(InputType.Aim, false, true);
repairTool.Use(deltaTime, character);
return;
}
} }
} }
} }
} }
} }
} }
private void OperateRepairTool(float deltaTime)
{
character.CursorPosition = Item.Position;
character.SetInput(InputType.Aim, false, true);
Vector2 fromToolToTarget = Item.Position - repairTool.Item.Position;
if (fromToolToTarget.LengthSquared() < MathUtils.Pow(repairTool.Range / 2, 2))
{
// Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - Item.SimPosition) / 2);
}
if (character.IsClimbing ||
VectorExtensions.Angle(VectorExtensions.Forward(repairTool.Item.body.TransformedRotation), fromToolToTarget) < MathHelper.PiOver4)
{
repairTool.Use(deltaTime, character);
}
}
} }
} }
@@ -51,8 +51,14 @@ namespace Barotrauma
{ {
foreach (Repairable repairable in item.Repairables) foreach (Repairable repairable in item.Repairables)
{ {
if (item.Condition > repairable.ShowRepairUIThreshold) { ignore = true; } if (!objectives.ContainsKey(item) && item.Condition > repairable.ShowRepairUIThreshold)
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character)) { ignore = true; } {
ignore = true;
}
else if (RequireAdequateSkills && !repairable.HasRequiredSkills(character))
{
ignore = true;
}
if (ignore) { break; } if (ignore) { break; }
} }
} }
@@ -203,7 +203,7 @@ namespace Barotrauma
{ {
DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed); DebugConsole.NewMessage("Pathfinding error, couldn't find a start node. "+ errorMsgStr, Color.DarkRed);
return new SteeringPath(); return new SteeringPath(true);
} }
closestDist = 0.0f; closestDist = 0.0f;
@@ -225,8 +225,12 @@ namespace Barotrauma
//if searching for a path inside the sub, make sure the waypoint is visible //if searching for a path inside the sub, make sure the waypoint is visible
if (insideSubmarine) if (insideSubmarine)
{ {
// TODO: for some reason fails to find the path when the sub is flooding. Disabling this check helps fixes it, but we can't disable it
var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition); var body = Submarine.CheckVisibility(end, node.Waypoint.SimPosition);
if (body != null && body.UserData is Structure) continue; if (body != null && body.UserData is Structure)
{
continue;
}
} }
closestDist = dist; closestDist = dist;
@@ -237,10 +241,9 @@ namespace Barotrauma
if (endNode == null) if (endNode == null)
{ {
DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed); DebugConsole.NewMessage("Pathfinding error, couldn't find an end node. " + errorMsgStr, Color.DarkRed);
return new SteeringPath(); return new SteeringPath(true);
} }
var path = FindPath(startNode, endNode); var path = FindPath(startNode, endNode);
return path; return path;
@@ -266,7 +269,7 @@ namespace Barotrauma
if (startNode == null || endNode == null) if (startNode == null || endNode == null)
{ {
DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed); DebugConsole.NewMessage("Pathfinding error, couldn't find matching pathnodes to waypoints.", Color.DarkRed);
return new SteeringPath(); return new SteeringPath(true);
} }
return FindPath(startNode, endNode); return FindPath(startNode, endNode);
@@ -294,9 +297,8 @@ namespace Barotrauma
start.state = 1; start.state = 1;
while (true) while (true)
{ {
PathNode currNode = null; PathNode currNode = null;
float dist = 10000.0f; float dist = float.MaxValue;
foreach (PathNode node in nodes) foreach (PathNode node in nodes)
{ {
if (node.state != 1) continue; if (node.state != 1) continue;
@@ -364,7 +366,7 @@ namespace Barotrauma
if (end.state == 0 || end.Parent == null) if (end.state == 0 || end.Parent == null)
{ {
//path not found //DebugConsole.NewMessage("Pathfinding error: path not found", Color.DarkRed);
return new SteeringPath(true); return new SteeringPath(true);
} }
@@ -683,12 +683,6 @@ namespace Barotrauma
limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength); limb.body.ApplyForce(diff * (float)(Math.Sin(WalkPos) * Math.Sqrt(limb.Mass)) * 30.0f * animStrength);
} }
while (referenceLimb.Rotation - angle < -MathHelper.TwoPi)
{
angle -= MathHelper.TwoPi;
}
limb?.body.SmoothRotate(angle, torque, wrapAngle: false);
} }
private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque) private void SmoothRotateWithoutWrapping(Limb limb, float angle, Limb referenceLimb, float torque)
@@ -1059,6 +1059,8 @@ namespace Barotrauma
} }
} }
private float prevFootPos;
void UpdateClimbing() void UpdateClimbing()
{ {
if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent<Ladder>() == null) if (character.SelectedConstruction == null || character.SelectedConstruction.GetComponent<Ladder>() == null)
@@ -1137,15 +1139,34 @@ namespace Barotrauma
handPos.X - Dir * 0.05f, handPos.X - Dir * 0.05f,
bottomPos + ColliderHeightFromFloor - stepHeight * 2.7f - ladderSimPos.Y); bottomPos + ColliderHeightFromFloor - stepHeight * 2.7f - ladderSimPos.Y);
MoveLimb(leftFoot, if (slide)
new Vector2(footPos.X, {
(slide ? footPos.Y : MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight) + ladderSimPos.Y), MoveLimb(leftFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true);
15.5f, true); MoveLimb(rightFoot, new Vector2(footPos.X, footPos.Y + ladderSimPos.Y), 15.5f, true);
}
else
{
float leftFootPos = MathUtils.Round(footPos.Y + stepHeight, stepHeight * 2.0f) - stepHeight;
float prevLeftFootPos = MathUtils.Round(prevFootPos + stepHeight, stepHeight * 2.0f) - stepHeight;
MoveLimb(leftFoot, new Vector2(footPos.X, leftFootPos + ladderSimPos.Y), 15.5f, true);
MoveLimb(rightFoot, float rightFootPos = MathUtils.Round(footPos.Y, stepHeight * 2.0f);
new Vector2(footPos.X, float prevRightFootPos = MathUtils.Round(prevFootPos, stepHeight * 2.0f);
(slide ? footPos.Y : MathUtils.Round(footPos.Y, stepHeight * 2.0f)) + ladderSimPos.Y), MoveLimb(rightFoot, new Vector2(footPos.X, rightFootPos + ladderSimPos.Y), 15.5f, true);
15.5f, true); #if CLIENT
if (Math.Abs(leftFootPos - prevLeftFootPos) > stepHeight && leftFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: leftFoot.WorldPosition);
leftFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
if (Math.Abs(rightFootPos - prevRightFootPos) > stepHeight && rightFoot.LastImpactSoundTime < Timing.TotalTime - Limb.SoundInterval)
{
SoundPlayer.PlaySound("footstep_armor_heavy", volume: 0.5f, range: 500.0f, position: rightFoot.WorldPosition);
rightFoot.LastImpactSoundTime = (float)Timing.TotalTime;
}
#endif
prevFootPos = footPos.Y;
}
//apply torque to the legs to make the knees bend //apply torque to the legs to make the knees bend
Limb leftLeg = GetLimb(LimbType.LeftLeg); Limb leftLeg = GetLimb(LimbType.LeftLeg);
@@ -1781,14 +1802,24 @@ namespace Barotrauma
float c = Vector2.Distance(pos, shoulderPos); float c = Vector2.Distance(pos, shoulderPos);
c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f); c = MathHelper.Clamp(c, Math.Abs(upperArmLength - forearmLength), forearmLength + upperArmLength - 0.01f);
float ang2 = MathUtils.VectorToAngle(pos - shoulderPos) + MathHelper.PiOver2; float armAngle = MathUtils.VectorToAngle(pos - shoulderPos) + MathHelper.PiOver2;
float armAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c); float upperArmAngle = MathUtils.SolveTriangleSSS(forearmLength, upperArmLength, c) * Dir;
float handAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c); float lowerArmAngle = MathUtils.SolveTriangleSSS(upperArmLength, forearmLength, c) * Dir;
arm?.body.SmoothRotate((ang2 - armAngle * Dir), 20.0f * force * arm.Mass); //make sure the arm angle "has the same number of revolutions" as the arm
forearm?.body.SmoothRotate((ang2 + handAngle * Dir), 20.0f * force * forearm.Mass); while (arm.Rotation - armAngle > MathHelper.Pi)
hand?.body.SmoothRotate((ang2 + handAngle * Dir), 100.0f * force * hand.Mass); {
armAngle += MathHelper.TwoPi;
}
while (arm.Rotation - armAngle < -MathHelper.Pi)
{
armAngle -= MathHelper.TwoPi;
}
arm?.body.SmoothRotate((armAngle - upperArmAngle), 20.0f * force * arm.Mass, wrapAngle: false);
forearm?.body.SmoothRotate((armAngle + lowerArmAngle), 20.0f * force * forearm.Mass, wrapAngle: false);
hand?.body.SmoothRotate((armAngle + lowerArmAngle), 100.0f * force * hand.Mass, wrapAngle: false);
} }
private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle) private void FootIK(Limb foot, Vector2 pos, float legTorque, float footTorque, float footAngle)
@@ -86,7 +86,10 @@ namespace Barotrauma
public bool onGround; public bool onGround;
private bool ignorePlatforms; private bool ignorePlatforms;
protected float ColliderHeightFromFloor => ConvertUnits.ToSimUnits(RagdollParams.ColliderHeightFromFloor) * RagdollParams.JointScale; /// <summary>
/// In sim units. Joint scale applied.
/// </summary>
public float ColliderHeightFromFloor => ConvertUnits.ToSimUnits(RagdollParams.ColliderHeightFromFloor) * RagdollParams.JointScale;
public Structure Stairs; public Structure Stairs;
@@ -1324,6 +1327,19 @@ namespace Barotrauma
} }
if (errorMsg != null) if (errorMsg != null)
{ {
if (character.IsRemotePlayer)
{
errorMsg += " Ragdoll controlled remotely.";
}
if (SimplePhysicsEnabled)
{
errorMsg += " Simple physics enabled.";
}
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
#if DEBUG #if DEBUG
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
#else #else
@@ -1343,7 +1359,6 @@ namespace Barotrauma
SetInitialLimbPositions(); SetInitialLimbPositions();
return; return;
} }
UpdateProjSpecific(deltaTime);
} }
partial void UpdateProjSpecific(float deltaTime); partial void UpdateProjSpecific(float deltaTime);
@@ -120,8 +120,6 @@ namespace Barotrauma
private List<StatusEffect> statusEffects = new List<StatusEffect>(); private List<StatusEffect> statusEffects = new List<StatusEffect>();
private List<float> speedMultipliers = new List<float>(); private List<float> speedMultipliers = new List<float>();
private List<StatusEffect> statusEffects = new List<StatusEffect>();
public Entity ViewTarget public Entity ViewTarget
{ {
get; get;
@@ -331,6 +329,7 @@ namespace Barotrauma
} }
} }
private float ragdollingLockTimer;
public bool IsRagdolled; public bool IsRagdolled;
public bool IsForceRagdolled; public bool IsForceRagdolled;
public bool dontFollowCursor; public bool dontFollowCursor;
@@ -1877,8 +1876,6 @@ namespace Barotrauma
} }
speechImpedimentSet = false; speechImpedimentSet = false;
if (needsAir) if (needsAir)
{ {
bool protectedFromPressure = PressureProtection > 0.0f; bool protectedFromPressure = PressureProtection > 0.0f;
@@ -1935,9 +1932,23 @@ namespace Barotrauma
//Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us! //Do ragdoll shenanigans before Stun because it's still technically a stun, innit? Less network updates for us!
bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true; bool allowRagdoll = GameMain.NetworkMember != null ? GameMain.NetworkMember.ServerSettings.AllowRagdollButton : true;
if (IsForceRagdolled) if (IsForceRagdolled)
{
IsRagdolled = IsForceRagdolled; IsRagdolled = IsForceRagdolled;
else if (allowRagdoll && (!IsRagdolled || AnimController.Collider.LinearVelocity.LengthSquared() < 1f)) //Keep us ragdolled if we were forced or we're too speedy to unragdoll }
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(); UpdateSightRange();
UpdateSoundRange(); UpdateSoundRange();
@@ -19,6 +19,10 @@ namespace Barotrauma
public readonly List<string> AllowedDialogTags; public readonly List<string> AllowedDialogTags;
private float commonness; private float commonness;
public float Commonness
{
get { return commonness; }
}
public NPCPersonalityTrait(XElement element) public NPCPersonalityTrait(XElement element)
{ {
@@ -457,20 +457,6 @@ namespace Barotrauma
} }
})); }));
#if CLIENT && WINDOWS
commands.Add(new Command("copyitemnames", "", (string[] args) =>
{
StringBuilder sb = new StringBuilder();
foreach (MapEntityPrefab mp in MapEntityPrefab.List)
{
if (!(mp is ItemPrefab)) continue;
sb.AppendLine(mp.Name);
}
System.Windows.Clipboard.SetText(sb.ToString());
}));
#endif
commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) => commands.Add(new Command("findentityids", "findentityids [entityname]", (string[] args) =>
{ {
if (args.Length == 0) return; if (args.Length == 0) return;
@@ -1435,58 +1421,25 @@ namespace Barotrauma
#if SERVER #if SERVER
if (GameMain.Server != null) if (GameMain.Server != null)
{ {
case "cursor": var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower());
spawnPos = cursorPos; if (client != null)
break; {
case "inventory": extraParams += 1;
spawnInventory = controlledCharacter?.Inventory; itemName = string.Join(" ", args.Take(args.Length - extraParams)).ToLowerInvariant();
break; if (client.Character != null && client.Character.Name == args.Last().ToLower()) spawnInventory = client.Character.Inventory;
case "cargo": itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
var wp = WayPoint.GetRandom(SpawnType.Cargo, null, Submarine.MainSub); }
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
break;
default:
//Check if last arg matches the name of an in-game player
if (GameMain.Server != null)
{
var client = GameMain.Server.ConnectedClients.Find(c => c.Name.ToLower() == args.Last().ToLower());
if (client == null)
{
NewMessage("No player found with the name \"" + args.Last() + "\". Spawning item at random location. If the player you want to give the item to has a space in their name, try surrounding their name with quotes (\").", Color.Red);
break;
}
else if (client.Character == null)
{
errorMsg = "The player \"" + args.Last() + "\" is connected, but hasn't spawned yet.";
return;
}
else
{
//If the last arg matches the name of an in-game player, set the destination to their inventory.
spawnInventory = client.Character.Inventory;
break;
}
}
else
{
var matchingCharacter = FindMatchingCharacter(args.Skip(1).ToArray());
if (matchingCharacter?.Inventory != null) spawnInventory = matchingCharacter.Inventory;
}
break;
} }
#endif #endif
} }
//Check again if the item can be found again after having checked for a character
string itemName = args[0];
var itemPrefab = MapEntityPrefab.Find(itemName) as ItemPrefab;
if (itemPrefab == null) if (itemPrefab == null)
{ {
errorMsg = "Item \"" + itemName + "\" not found!"; errorMsg = "Item \"" + itemName + "\" not found!";
return; return;
} }
if (spawnPos == null && spawnInventory == null) if ((spawnPos == null || spawnPos == Vector2.Zero) && spawnInventory == null)
{ {
var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub); var wp = WayPoint.GetRandom(SpawnType.Human, null, Submarine.MainSub);
spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition; spawnPos = wp == null ? Vector2.Zero : wp.WorldPosition;
@@ -1495,6 +1448,7 @@ namespace Barotrauma
if (spawnPos != null) if (spawnPos != null)
{ {
Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos); Entity.Spawner.AddToSpawnQueue(itemPrefab, (Vector2)spawnPos);
} }
else if (spawnInventory != null) else if (spawnInventory != null)
{ {
@@ -12,7 +12,7 @@ namespace Barotrauma
public bool CheatsEnabled; public bool CheatsEnabled;
const int InitialMoney = 4500; const int InitialMoney = 4700;
protected bool watchmenSpawned; protected bool watchmenSpawned;
protected Character startWatchman, endWatchman; protected Character startWatchman, endWatchman;
@@ -45,8 +45,6 @@ namespace Barotrauma
private static byte currentCampaignID; private static byte currentCampaignID;
private List<CharacterCampaignData> characterData = new List<CharacterCampaignData>();
public byte CampaignID public byte CampaignID
{ {
get; private set; get; private set;
@@ -217,7 +215,7 @@ namespace Barotrauma
break; break;
} }
} }
#if SERVER
characterData.Clear(); characterData.Clear();
string characterDataPath = GetCharacterDataSavePath(); string characterDataPath = GetCharacterDataSavePath();
var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath); var characterDataDoc = XMLExtensions.TryLoadXml(characterDataPath);
@@ -226,33 +224,8 @@ namespace Barotrauma
{ {
characterData.Add(new CharacterCampaignData(subElement)); characterData.Add(new CharacterCampaignData(subElement));
} }
#endif
} }
public override void Save(XElement element)
{
XElement modeElement = new XElement("MultiPlayerCampaign",
new XAttribute("money", Money),
new XAttribute("cheatsenabled", CheatsEnabled));
Map.Save(modeElement);
element.Add(modeElement);
//save character data to a separate file
string characterDataPath = GetCharacterDataSavePath();
XDocument characterDataDoc = new XDocument(new XElement("CharacterData"));
foreach (CharacterCampaignData cd in characterData)
{
characterDataDoc.Root.Add(cd.Save());
}
try
{
characterDataDoc.Save(characterDataPath);
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving multiplayer campaign characters to \"" + characterDataPath + "\" failed!", e);
}
lastSaveID++;
}
} }
} }
@@ -39,34 +39,6 @@ namespace Barotrauma
public int ParticleLimit { get; set; } public int ParticleLimit { get; set; }
public int ParticleLimit { get; set; }
public float LightMapScale { get; set; }
public bool SpecularityEnabled { get; set; }
public bool ChromaticAberrationEnabled { get; set; }
public int ParticleLimit { get; set; }
public float LightMapScale { get; set; }
public bool SpecularityEnabled { get; set; }
public bool ChromaticAberrationEnabled { get; set; }
public bool MuteOnFocusLost { get; set; }
public enum VoiceMode
{
Disabled,
PushToTalk,
Activity
};
public VoiceMode VoiceSetting { get; set; }
public string VoiceCaptureDevice { get; set; }
public int ParticleLimit { get; set; }
public int ParticleLimit { get; set; }
public float LightMapScale { get; set; } public float LightMapScale { get; set; }
public bool SpecularityEnabled { get; set; } public bool SpecularityEnabled { get; set; }
public bool ChromaticAberrationEnabled { get; set; } public bool ChromaticAberrationEnabled { get; set; }
@@ -196,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 public float SoundVolume
{ {
@@ -239,6 +211,14 @@ namespace Barotrauma
} }
} }
public float MicrophoneVolume
{
get { return microphoneVolume; }
set
{
microphoneVolume = MathHelper.Clamp(value, 0.1f, 5.0f);
}
}
public string Language public string Language
{ {
get { return TextManager.Language; } get { return TextManager.Language; }
@@ -397,10 +377,6 @@ namespace Barotrauma
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f); AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", 0.5f);
keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length]; keyMapping = new KeyOrMouse[Enum.GetNames(typeof(InputType)).Length];
keyMapping[(int)InputType.Up] = new KeyOrMouse(Keys.W); keyMapping[(int)InputType.Up] = new KeyOrMouse(Keys.W);
keyMapping[(int)InputType.Down] = new KeyOrMouse(Keys.S); keyMapping[(int)InputType.Down] = new KeyOrMouse(Keys.S);
@@ -514,70 +490,6 @@ namespace Barotrauma
TextManager.LoadTextPacks(SelectedContentPackages); TextManager.LoadTextPacks(SelectedContentPackages);
//display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths)
{
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
}
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
{
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
.Replace("[packagename]", incompatiblePackage.Name)
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
.Replace("[gameversion]", GameMain.Version.ToString()));
}
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
TextManager.LoadTextPacks(SelectedContentPackages);
//display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths)
{
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
}
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
{
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
.Replace("[packagename]", incompatiblePackage.Name)
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
.Replace("[gameversion]", GameMain.Version.ToString()));
}
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
if (!SelectedContentPackages.Any())
{
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
if (availablePackage != null)
{
SelectedContentPackages.Add(availablePackage);
}
}
TextManager.LoadTextPacks(SelectedContentPackages);
//display error messages after all content packages have been loaded //display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager //to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths) foreach (string missingPackagePath in missingPackagePaths)
@@ -1021,369 +933,7 @@ namespace Barotrauma
gMode = new XElement("graphicsmode"); gMode = new XElement("graphicsmode");
doc.Root.Add(gMode); doc.Root.Add(gMode);
} }
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
}
else
{
gMode.ReplaceAttributes(
new XAttribute("width", GraphicsWidth),
new XAttribute("height", GraphicsHeight),
new XAttribute("vsync", VSyncEnabled),
new XAttribute("displaymode", windowMode));
}
XElement gSettings = doc.Root.Element("graphicssettings");
if (gSettings == null)
{
gSettings = new XElement("graphicssettings");
doc.Root.Add(gSettings);
}
gSettings.ReplaceAttributes(
new XAttribute("particlelimit", ParticleLimit),
new XAttribute("lightmapscale", LightMapScale),
new XAttribute("specularity", SpecularityEnabled),
new XAttribute("chromaticaberration", ChromaticAberrationEnabled),
new XAttribute("losmode", LosMode),
new XAttribute("hudscale", HUDScale),
new XAttribute("inventoryscale", InventoryScale));
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
if (contentPackage.Path.Contains(vanillaContentPackagePath))
{
doc.Root.Add(new XElement("contentpackage", new XAttribute("path", contentPackage.Path)));
break;
}
}
var keyMappingElement = new XElement("keymapping");
doc.Root.Add(keyMappingElement);
for (int i = 0; i < keyMapping.Length; i++)
{
if (keyMapping[i].MouseButton == null)
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].Key));
}
else
{
keyMappingElement.Add(new XAttribute(((InputType)i).ToString(), keyMapping[i].MouseButton));
}
}
var gameplay = new XElement("gameplay");
var jobPreferences = new XElement("jobpreferences");
foreach (string jobName in JobPreferences)
{
jobPreferences.Add(new XElement("job", new XAttribute("identifier", jobName)));
}
gameplay.Add(jobPreferences);
doc.Root.Add(gameplay);
var playerElement = new XElement("player",
new XAttribute("name", defaultPlayerName ?? ""),
new XAttribute("headindex", CharacterHeadIndex),
new XAttribute("gender", CharacterGender),
new XAttribute("race", CharacterRace),
new XAttribute("hairindex", CharacterHairIndex),
new XAttribute("beardindex", CharacterBeardIndex),
new XAttribute("moustacheindex", CharacterMoustacheIndex),
new XAttribute("faceattachmentindex", CharacterFaceAttachmentIndex));
doc.Root.Add(playerElement);
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
OmitXmlDeclaration = true,
NewLineOnAttributes = true
};
try
{
using (var writer = XmlWriter.Create(savePath, settings))
{
doc.WriteTo(writer);
writer.Flush();
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Saving game settings failed.", e);
GameAnalyticsManager.AddErrorEventOnce("GameSettings.Save:SaveFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Saving game settings failed.\n" + e.Message + "\n" + e.StackTrace);
}
}
#endregion
#region Load PlayerConfig
// TODO: DRY
public void LoadPlayerConfig()
{
XDocument doc = XMLExtensions.LoadXml(playerSavePath);
if (doc == null || doc.Root == null)
{
ShowUserStatisticsPrompt = true;
SaveNewPlayerConfig();
return;
}
Language = doc.Root.GetAttributeString("language", Language);
AutoCheckUpdates = doc.Root.GetAttributeBool("autocheckupdates", AutoCheckUpdates);
sendUserStatistics = doc.Root.GetAttributeBool("senduserstatistics", true);
XElement graphicsMode = doc.Root.Element("graphicsmode");
GraphicsWidth = graphicsMode.GetAttributeInt("width", GraphicsWidth);
GraphicsHeight = graphicsMode.GetAttributeInt("height", GraphicsHeight);
VSyncEnabled = graphicsMode.GetAttributeBool("vsync", VSyncEnabled);
XElement graphicsSettings = doc.Root.Element("graphicssettings");
ParticleLimit = graphicsSettings.GetAttributeInt("particlelimit", ParticleLimit);
LightMapScale = MathHelper.Clamp(graphicsSettings.GetAttributeFloat("lightmapscale", LightMapScale), 0.1f, 1.0f);
SpecularityEnabled = graphicsSettings.GetAttributeBool("specularity", SpecularityEnabled);
ChromaticAberrationEnabled = graphicsSettings.GetAttributeBool("chromaticaberration", ChromaticAberrationEnabled);
HUDScale = graphicsSettings.GetAttributeFloat("hudscale", HUDScale);
InventoryScale = graphicsSettings.GetAttributeFloat("inventoryscale", InventoryScale);
var losModeStr = graphicsSettings.GetAttributeString("losmode", "Transparent");
if (!Enum.TryParse(losModeStr, out losMode))
{
losMode = LosMode.Transparent;
}
#if CLIENT
if (GraphicsWidth == 0 || GraphicsHeight == 0)
{
GraphicsWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
GraphicsHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
}
#endif
var windowModeStr = graphicsMode.GetAttributeString("displaymode", "Fullscreen");
if (!Enum.TryParse(windowModeStr, out windowMode))
{
windowMode = WindowMode.Fullscreen;
}
XElement audioSettings = doc.Root.Element("audio");
if (audioSettings != null)
{
SoundVolume = audioSettings.GetAttributeFloat("soundvolume", SoundVolume);
MusicVolume = audioSettings.GetAttributeFloat("musicvolume", MusicVolume);
VoiceChatVolume = audioSettings.GetAttributeFloat("voicechatvolume", VoiceChatVolume);
string voiceSettingStr = audioSettings.GetAttributeString("voicesetting", "Disabled");
VoiceCaptureDevice = audioSettings.GetAttributeString("voicecapturedevice", "");
NoiseGateThreshold = audioSettings.GetAttributeFloat("noisegatethreshold", -45);
var voiceSetting = VoiceMode.Disabled;
if (Enum.TryParse(voiceSettingStr, out voiceSetting))
{
VoiceSetting = voiceSetting;
}
}
useSteamMatchmaking = doc.Root.GetAttributeBool("usesteammatchmaking", useSteamMatchmaking);
requireSteamAuthentication = doc.Root.GetAttributeBool("requiresteamauthentication", requireSteamAuthentication);
EnableSplashScreen = doc.Root.GetAttributeBool("enablesplashscreen", EnableSplashScreen);
AimAssistAmount = doc.Root.GetAttributeFloat("aimassistamount", AimAssistAmount);
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "keymapping":
foreach (XAttribute attribute in subElement.Attributes())
{
if (Enum.TryParse(attribute.Name.ToString(), true, out InputType inputType))
{
if (int.TryParse(attribute.Value.ToString(), out int mouseButton))
{
keyMapping[(int)inputType] = new KeyOrMouse(mouseButton);
}
else
{
if (Enum.TryParse(attribute.Value.ToString(), true, out Keys key))
{
keyMapping[(int)inputType] = new KeyOrMouse(key);
}
}
}
}
break;
case "gameplay":
jobPreferences = new List<string>();
foreach (XElement ele in subElement.Element("jobpreferences").Elements("job"))
{
string jobIdentifier = ele.GetAttributeString("identifier", "");
if (string.IsNullOrEmpty(jobIdentifier)) continue;
jobPreferences.Add(jobIdentifier);
}
break;
case "player":
defaultPlayerName = subElement.GetAttributeString("name", defaultPlayerName);
CharacterHeadIndex = subElement.GetAttributeInt("headindex", CharacterHeadIndex);
if (Enum.TryParse(subElement.GetAttributeString("gender", "none"), true, out Gender g))
{
CharacterGender = g;
}
if (Enum.TryParse(subElement.GetAttributeString("race", "white"), true, out Race r))
{
CharacterRace = r;
}
else
{
CharacterRace = Race.White;
}
CharacterHairIndex = subElement.GetAttributeInt("hairindex", CharacterHairIndex);
CharacterBeardIndex = subElement.GetAttributeInt("beardindex", CharacterBeardIndex);
CharacterMoustacheIndex = subElement.GetAttributeInt("moustacheindex", CharacterMoustacheIndex);
CharacterFaceAttachmentIndex = subElement.GetAttributeInt("faceattachmentindex", CharacterFaceAttachmentIndex);
break;
case "tutorials":
foreach (XElement tutorialElement in subElement.Elements())
{
CompletedTutorialNames.Add(tutorialElement.GetAttributeString("name", ""));
}
break;
}
}
foreach (InputType inputType in Enum.GetValues(typeof(InputType)))
{
if (keyMapping[(int)inputType] == null)
{
DebugConsole.ThrowError("Key binding for the input type \"" + inputType + " not set!");
keyMapping[(int)inputType] = new KeyOrMouse(Keys.D1);
}
}
UnsavedSettings = false;
selectedContentPackagePaths = new HashSet<string>();
foreach (XElement subElement in doc.Root.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "contentpackage":
string path = System.IO.Path.GetFullPath(subElement.GetAttributeString("path", ""));
selectedContentPackagePaths.Add(path);
break;
}
}
LoadContentPackages(selectedContentPackagePaths);
}
public void ReloadContentPackages()
{
LoadContentPackages(selectedContentPackagePaths);
}
private void LoadContentPackages(IEnumerable<string> contentPackagePaths)
{
var missingPackagePaths = new List<string>();
var incompatiblePackages = new List<ContentPackage>();
SelectedContentPackages.Clear();
foreach (string path in contentPackagePaths)
{
var matchingContentPackage = ContentPackage.List.Find(cp => System.IO.Path.GetFullPath(cp.Path) == path);
if (matchingContentPackage == null)
{
missingPackagePaths.Add(path);
}
else if (!matchingContentPackage.IsCompatible())
{
incompatiblePackages.Add(matchingContentPackage);
}
else
{
SelectedContentPackages.Add(matchingContentPackage);
}
}
TextManager.LoadTextPacks(SelectedContentPackages);
foreach (ContentPackage contentPackage in SelectedContentPackages)
{
foreach (ContentFile file in contentPackage.Files)
{
if (!System.IO.File.Exists(file.Path))
{
DebugConsole.ThrowError("Error in content package \"" + contentPackage.Name + "\" - file \"" + file.Path + "\" not found.");
continue;
}
ToolBox.IsProperFilenameCase(file.Path);
}
}
if (!SelectedContentPackages.Any())
{
var availablePackage = ContentPackage.List.FirstOrDefault(cp => cp.IsCompatible() && cp.CorePackage);
if (availablePackage != null)
{
SelectedContentPackages.Add(availablePackage);
}
}
//save to get rid of the invalid selected packages in the config file
if (missingPackagePaths.Count > 0 || incompatiblePackages.Count > 0) { SaveNewPlayerConfig(); }
//display error messages after all content packages have been loaded
//to make sure the package that contains text files has been loaded before we attempt to use TextManager
foreach (string missingPackagePath in missingPackagePaths)
{
DebugConsole.ThrowError(TextManager.Get("ContentPackageNotFound").Replace("[packagepath]", missingPackagePath));
}
foreach (ContentPackage incompatiblePackage in incompatiblePackages)
{
DebugConsole.ThrowError(TextManager.Get(incompatiblePackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage")
.Replace("[packagename]", incompatiblePackage.Name)
.Replace("[packageversion]", incompatiblePackage.GameVersion.ToString())
.Replace("[gameversion]", GameMain.Version.ToString()));
}
}
#endregion
#region Save PlayerConfig
public void SaveNewPlayerConfig()
{
XDocument doc = new XDocument();
UnsavedSettings = false;
if (doc.Root == null)
{
doc.Add(new XElement("config"));
}
doc.Root.Add(
new XAttribute("language", TextManager.Language),
new XAttribute("masterserverurl", MasterServerUrl),
new XAttribute("autocheckupdates", AutoCheckUpdates),
new XAttribute("musicvolume", musicVolume),
new XAttribute("soundvolume", soundVolume),
new XAttribute("verboselogging", VerboseLogging),
new XAttribute("savedebugconsolelogs", SaveDebugConsoleLogs),
new XAttribute("enablesplashscreen", EnableSplashScreen),
new XAttribute("usesteammatchmaking", useSteamMatchmaking),
new XAttribute("quickstartsub", QuickStartSubmarineName),
new XAttribute("requiresteamauthentication", requireSteamAuthentication),
new XAttribute("autoupdateworkshopitems", AutoUpdateWorkshopItems),
new XAttribute("aimassistamount", aimAssistAmount));
if (!ShowUserStatisticsPrompt)
{
doc.Root.Add(new XAttribute("senduserstatistics", sendUserStatistics));
}
XElement gMode = doc.Root.Element("graphicsmode");
if (gMode == null)
{
gMode = new XElement("graphicsmode");
doc.Root.Add(gMode);
}
if (GraphicsWidth == 0 || GraphicsHeight == 0) if (GraphicsWidth == 0 || GraphicsHeight == 0)
{ {
gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode)); gMode.ReplaceAttributes(new XAttribute("displaymode", windowMode));
@@ -641,7 +641,7 @@ namespace Barotrauma.Items.Components
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]); if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
} }
//make sure the left hull is linked to the gap first (gap logic assumes that the first hull is the one to the left) //make sure the left hull is linked to the gap first (gap logic assumes that the first hull is the one to the left)
if (doorGap.linkedTo[0].Rect.X > doorGap.linkedTo[1].Rect.X) if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].Rect.X > doorGap.linkedTo[1].Rect.X)
{ {
var temp = doorGap.linkedTo[0]; var temp = doorGap.linkedTo[0];
doorGap.linkedTo[0] = doorGap.linkedTo[1]; doorGap.linkedTo[0] = doorGap.linkedTo[1];
@@ -659,7 +659,7 @@ namespace Barotrauma.Items.Components
if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]); if (!doorGap.linkedTo.Contains(hulls[1])) doorGap.linkedTo.Add(hulls[1]);
} }
//make sure the upper hull is linked to the gap first (gap logic assumes that the first hull is above the second one) //make sure the upper hull is linked to the gap first (gap logic assumes that the first hull is above the second one)
if (doorGap.linkedTo[0].Rect.Y < doorGap.linkedTo[1].Rect.Y) if (doorGap.linkedTo.Count > 1 && doorGap.linkedTo[0].Rect.Y < doorGap.linkedTo[1].Rect.Y)
{ {
var temp = doorGap.linkedTo[0]; var temp = doorGap.linkedTo[0];
doorGap.linkedTo[0] = doorGap.linkedTo[1]; doorGap.linkedTo[0] = doorGap.linkedTo[1];
@@ -51,6 +51,11 @@ namespace Barotrauma.Items.Components
public PhysicsBody Body { get; private set; } public PhysicsBody Body { get; private set; }
private float RepairThreshold
{
get { return item.GetComponent<Repairable>()?.ShowRepairUIThreshold ?? 0.0f; }
}
private float stuck; private float stuck;
[Serialize(0.0f, false)] [Serialize(0.0f, false)]
public float Stuck public float Stuck
@@ -207,7 +212,7 @@ namespace Barotrauma.Items.Components
public override bool HasRequiredItems(Character character, bool addMessage) public override bool HasRequiredItems(Character character, bool addMessage)
{ {
if (item.Condition <= 0.0f) return true; //For repairing if (item.Condition <= RepairThreshold) return true; //For repairing
//this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing. //this is a bit pointless atm because if canBePicked is false it won't allow you to do Pick() anyway, however it's still good for future-proofing.
return requiredItems.Any() ? base.HasRequiredItems(character, addMessage) : canBePicked; return requiredItems.Any() ? base.HasRequiredItems(character, addMessage) : canBePicked;
@@ -215,12 +220,12 @@ namespace Barotrauma.Items.Components
public override bool Pick(Character picker) public override bool Pick(Character picker)
{ {
return item.Condition <= 0.0f ? true : base.Pick(picker); return item.Condition <= RepairThreshold ? true : base.Pick(picker);
} }
public override bool OnPicked(Character picker) public override bool OnPicked(Character picker)
{ {
if (item.Condition <= 0.0f) return true; //repairs if (item.Condition <= RepairThreshold) return true; //repairs
SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function SetState(PredictedState == null ? !isOpen : !PredictedState.Value, false, true); //crowbar function
#if CLIENT #if CLIENT
@@ -232,7 +237,7 @@ namespace Barotrauma.Items.Components
public override bool Select(Character character) public override bool Select(Character character)
{ {
//can only be selected if the item is broken //can only be selected if the item is broken
return item.Condition <= 0.0f; return item.Condition <= RepairThreshold;
} }
public override void Update(float deltaTime, Camera cam) public override void Update(float deltaTime, Camera cam)
@@ -141,6 +141,7 @@ namespace Barotrauma.Items.Components
partial void UseProjSpecific(float deltaTime); partial void UseProjSpecific(float deltaTime);
private List<FireSource> fireSourcesInRange = new List<FireSource>();
private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies) private void Repair(Vector2 rayStart, Vector2 rayEnd, float deltaTime, Character user, float degreeOfSuccess, List<Body> ignoredBodies)
{ {
var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair; var collisionCategories = Physics.CollisionWall | Physics.CollisionCharacter | Physics.CollisionItem | Physics.CollisionLevel | Physics.CollisionRepair;
@@ -159,7 +160,7 @@ namespace Barotrauma.Items.Components
if (ExtinguishAmount > 0.0f && item.CurrentHull != null) if (ExtinguishAmount > 0.0f && item.CurrentHull != null)
{ {
List<FireSource> fireSourcesInRange = new List<FireSource>(); fireSourcesInRange.Clear();
//step along the ray in 10% intervals, collecting all fire sources in the range //step along the ray in 10% intervals, collecting all fire sources in the range
for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f) for (float x = 0.0f; x <= Submarine.LastPickedFraction; x += 0.1f)
{ {
@@ -252,11 +253,11 @@ namespace Barotrauma.Items.Components
} }
} }
partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex); partial void FixStructureProjSpecific(Character user, float deltaTime, Structure targetStructure, int sectionIndex);
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter); partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter);
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition); partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem, float prevCondition);
private float sinTime;
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective) public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{ {
Gap leak = objective.OperateTarget as Gap; Gap leak = objective.OperateTarget as Gap;
@@ -280,18 +281,26 @@ namespace Barotrauma.Items.Components
{ {
Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f); Vector2 standPos = leak.IsHorizontal ? new Vector2(Math.Sign(-fromItemToLeak.X), 0.0f) : new Vector2(0.0f, Math.Sign(-fromItemToLeak.Y) * 0.5f);
standPos = leak.WorldPosition + standPos * Range; standPos = leak.WorldPosition + standPos * Range;
// TODO: check if too close to the stand pos -> move away so that the tool can hit the target and not through it?
Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition); Vector2 dir = Vector2.Normalize(standPos - character.WorldPosition);
character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2); character.AIController.SteeringManager.SteeringManual(deltaTime, dir / 2);
} }
else else
{ {
// TODO: sometimes stuck here, if too close to the target if (dist < Range / 2)
//close enough -> stop moving {
character.AIController.SteeringManager.Reset(); // Too close -> steer away
character.AIController.SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.SimPosition - leak.SimPosition) / 2);
}
else if (!character.IsClimbing)
{
// Close enough -> stop if not in ladders.
// In ladders, we most likely want to move back and forth, because we cannot aim -> if the leak is on the side, it should get fixed.
character.AIController.SteeringManager.Reset();
}
} }
character.CursorPosition = leak.Position; sinTime += deltaTime;
character.CursorPosition = leak.Position + VectorExtensions.Forward(Item.body.TransformedRotation + (float)Math.Sin(sinTime), dist);
character.SetInput(InputType.Aim, false, true); character.SetInput(InputType.Aim, false, true);
// Press the trigger only when the tool is approximately facing the target. // Press the trigger only when the tool is approximately facing the target.
@@ -300,12 +309,17 @@ namespace Barotrauma.Items.Components
{ {
Use(deltaTime, character); Use(deltaTime, character);
} }
else
{
sinTime -= deltaTime * 2;
}
// TODO: fix until the wall is fixed? bool leakFixed = (leak.Open <= 0.0f || leak.Removed) &&
bool leakFixed = leak.Open <= 0.0f || leak.Removed; (leak.ConnectedWall == null || leak.ConnectedWall.Sections.Average(s => s.damage) < 1);
if (leakFixed && leak.FlowTargetHull != null) if (leakFixed && leak.FlowTargetHull != null)
{ {
sinTime = 0;
if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f)) if (!leak.FlowTargetHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f))
{ {
character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f); character.Speak(TextManager.Get("DialogLeaksFixed").Replace("[roomname]", leak.FlowTargetHull.RoomName), null, 0.0f, "leaksfixed", 10.0f);
@@ -535,7 +535,6 @@ namespace Barotrauma.Items.Components
GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("ItemComponent.DegreeOfSuccess:CharacterNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return 0.0f; return 0.0f;
} }
float average = skillSuccessSum / requiredSkills.Count;
float skillSuccessSum = 0.0f; float skillSuccessSum = 0.0f;
for (int i = 0; i < requiredSkills.Count; i++) for (int i = 0; i < requiredSkills.Count; i++)
@@ -155,29 +155,5 @@ namespace Barotrauma.Items.Components
} }
} }
} }
public void ServerWrite(NetBuffer msg, Client c, object[] extraData = null)
{
//force can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f));
}
public void ServerRead(ClientNetObject type, NetBuffer msg, Client c)
{
float newTargetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
if (item.CanClientAccess(c))
{
if (Math.Abs(newTargetForce - targetForce) > 0.01f)
{
GameServer.Log(c.Character.LogName + " set the force of " + item.Name + " to " + (int)(newTargetForce) + " %", ServerLog.MessageType.ItemInteraction);
}
targetForce = newTargetForce;
}
//notify all clients of the changed state
item.CreateServerEvent(this);
}
} }
} }
@@ -160,7 +160,6 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam) public override void Update(float deltaTime, Camera cam)
{ {
UpdateOnActiveEffects(deltaTime); UpdateOnActiveEffects(deltaTime);
if (AITarget != null) AITarget.Enabled = voltage > minVoltage || powerConsumption <= 0.0f;
#if CLIENT #if CLIENT
light.ParentSub = item.Submarine; light.ParentSub = item.Submarine;
@@ -178,11 +178,20 @@ namespace Barotrauma.Items.Components
newConnection.Item.Position : newConnection.Item.Position :
newConnection.Item.Position - refSub.HiddenSubPosition; newConnection.Item.Position - refSub.HiddenSubPosition;
if (nodes.Count > 0 && nodes[0] == nodePos) break; if (nodes.Count > 0 && nodes[0] == nodePos) break;
if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break; if (nodes.Count > 1 && nodes[nodes.Count - 1] == nodePos) break;
if (i == 0) //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); nodes.Insert(0, nodePos);
} }
@@ -433,9 +433,10 @@ namespace Barotrauma.Items.Components
if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill")) if (usableProjectileCount == 0 || (usableProjectileCount < maxProjectileCount && objective.Option.ToLowerInvariant() != "fireatwill"))
{ {
ItemContainer container = null; ItemContainer container = null;
Item containerItem = null;
foreach (MapEntity e in item.linkedTo) foreach (MapEntity e in item.linkedTo)
{ {
var containerItem = e as Item; containerItem = e as Item;
if (containerItem == null) continue; if (containerItem == null) continue;
container = containerItem.GetComponent<ItemContainer>(); container = containerItem.GetComponent<ItemContainer>();
@@ -453,7 +454,7 @@ namespace Barotrauma.Items.Components
var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container); var containShellObjective = new AIObjectiveContainItem(character, container.ContainableItems[0].Identifiers[0], container);
character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f); character?.Speak(TextManager.Get("DialogLoadTurret").Replace("[itemname]", item.Name), null, 0.0f, "loadturret", 30.0f);
containShellObjective.MinContainedAmount = usableProjectileCount + 1; containShellObjective.MinContainedAmount = usableProjectileCount + 1;
containShellObjective.IgnoreAlreadyContainedItems = true; containShellObjective.ignoredContainerIdentifiers = new string[] { containerItem.prefab.Identifier };
objective.AddSubObjective(containShellObjective); objective.AddSubObjective(containShellObjective);
return false; return false;
} }
@@ -1631,12 +1631,13 @@ namespace Barotrauma
SerializableProperty property = extraData[1] as SerializableProperty; SerializableProperty property = extraData[1] as SerializableProperty;
if (property != null) if (property != null)
{ {
var propertyOwner = allProperties.Find(p => p.Second == property);
if (allProperties.Count > 1) if (allProperties.Count > 1)
{ {
msg.WriteRangedInteger(0, allProperties.Count - 1, allProperties.FindIndex(p => p.Second == property)); msg.WriteRangedInteger(0, allProperties.Count - 1, allProperties.FindIndex(p => p.Second == property));
} }
object value = property.GetValue(this); object value = property.GetValue(propertyOwner.First);
if (value is string) if (value is string)
{ {
msg.Write((string)value); msg.Write((string)value);
@@ -89,7 +89,9 @@ namespace Barotrauma
} }
CommonnessPerZone[zoneIndex] = zoneCommonness; CommonnessPerZone[zoneIndex] = zoneCommonness;
} }
catch (Exception e)
hireableJobs = new List<Tuple<JobPrefab, float>>();
foreach (XElement subElement in element.Elements())
{ {
switch (subElement.Name.ToString().ToLowerInvariant()) switch (subElement.Name.ToString().ToLowerInvariant())
{ {
@@ -269,9 +269,6 @@ namespace Barotrauma
} }
} }
//remove orphans
Locations.RemoveAll(c => !connectedLocations.Contains(c));
for (int i = connections.Count - 1; i >= 0; i--) for (int i = connections.Count - 1; i >= 0; i--)
{ {
i = Math.Min(i, connections.Count - 1); i = Math.Min(i, connections.Count - 1);
@@ -428,7 +425,6 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return; return;
} }
CurrentLocation.SelectedMissionIndex = missionIndex;
SelectedLocation = location; SelectedLocation = location;
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation)); SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
@@ -134,7 +134,7 @@ namespace Barotrauma
public HashSet<string> Tags public HashSet<string> Tags
{ {
get { return prefab; } get { return prefab.Tags; }
} }
protected Color spriteColor; protected Color spriteColor;
@@ -523,30 +523,6 @@ namespace Barotrauma
{ {
maxX = Math.Min(maxX, ruin.Area.X - 100.0f); maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
} }
else
{
maxX = Math.Min(maxX, ruin.Area.X - 100.0f);
}
}
if (minX < 0.0f && maxX > Level.Loaded.Size.X)
{
//no walls found at either side, just use the initial spawnpos and hope for the best
}
else if (minX < 0)
{
//no wall found at the left side, spawn to the left from the right-side wall
spawnPos.X = maxX - minWidth - 100.0f;
}
else if (maxX > Level.Loaded.Size.X)
{
//no wall found at right side, spawn to the right from the left-side wall
spawnPos.X = minX + minWidth + 100.0f;
}
else
{
//walls found at both sides, use their midpoint
spawnPos.X = (minX + maxX) / 2;
} }
if (minX < 0.0f && maxX > Level.Loaded.Size.X) if (minX < 0.0f && maxX > Level.Loaded.Size.X)
@@ -1016,26 +992,6 @@ namespace Barotrauma
return closest; return closest;
} }
public List<Hull> GetHulls(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Hull.hullList);
public List<Gap> GetGaps(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Gap.GapList);
public List<Item> GetItems(bool alsoFromConnectedSubs) => GetEntities(alsoFromConnectedSubs, Item.ItemList);
public List<T> GetEntities<T>(bool includingConnectedSubs, List<T> list) where T : MapEntity
{
return list.FindAll(e => IsEntityFoundOnThisSub(e, includingConnectedSubs));
}
public bool IsEntityFoundOnThisSub(MapEntity entity, bool includingConnectedSubs)
{
if (entity.Submarine == this) { return true; }
if (entity.Submarine == null) { return false; }
if (includingConnectedSubs)
{
return GetConnectedSubs().Any(s => s == entity.Submarine && entity.Submarine.TeamID == TeamID);
}
return false;
}
/// <summary> /// <summary>
/// Returns true if the sub is same as the other. /// Returns true if the sub is same as the other.
/// </summary> /// </summary>
@@ -700,6 +700,23 @@ namespace Barotrauma
Vector2 impulse = direction * impact * 0.5f; Vector2 impulse = direction * impact * 0.5f;
impulse = impulse.ClampLength(5.0f); 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 CLIENT
if (Character.Controlled != null && Character.Controlled.Submarine == submarine) if (Character.Controlled != null && Character.Controlled.Submarine == submarine)
{ {
@@ -522,7 +522,12 @@ namespace Barotrauma
string errorMsg = string errorMsg =
"Attempted to apply invalid " + valueName + "Attempted to apply invalid " + valueName +
" to a physics body (userdata: " + userData + " to a physics body (userdata: " + userData +
"), value: " + value + "\n" + Environment.StackTrace; "), value: " + value;
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
errorMsg += "\n" + Environment.StackTrace;
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
@@ -544,7 +549,12 @@ namespace Barotrauma
string errorMsg = string errorMsg =
"Attempted to apply invalid " + valueName + "Attempted to apply invalid " + valueName +
" to a physics body (userdata: " + userData + " to a physics body (userdata: " + userData +
"), value: " + value + "\n" + Environment.StackTrace; "), value: " + value;
if (GameMain.NetworkMember != null)
{
errorMsg += GameMain.NetworkMember.IsClient ? " Playing as a client." : " Hosting a server.";
}
errorMsg += "\n" + Environment.StackTrace;
if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg); if (GameSettings.VerboseLogging) DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
@@ -114,13 +114,6 @@ namespace Barotrauma
private bool hit, hitQueue; private bool hit, hitQueue;
private bool held, heldQueue; private bool held, heldQueue;
#if CLIENT
private InputType inputType;
public Key(InputType inputType)
{
this.inputType = inputType;
}
private InputType inputType; private InputType inputType;
@@ -138,17 +131,6 @@ namespace Barotrauma
{ {
get { return binding; } get { return binding; }
} }
#endif
public void SetState()
{
hit = binding.IsHit();
if (hit) hitQueue = true;
held = binding.IsDown();
if (held) heldQueue = true;
}
#endif
public void SetState() public void SetState()
{ {
@@ -718,11 +718,6 @@ namespace Barotrauma
} }
} }
bool isNotClient = true;
#if CLIENT
isNotClient = GameMain.Client == null;
#endif
if (FireSize > 0.0f && entity != null) if (FireSize > 0.0f && entity != null)
{ {
var fire = new FireSource(entity.WorldPosition, hull); var fire = new FireSource(entity.WorldPosition, hull);
@@ -153,49 +153,59 @@ namespace Barotrauma
string[] messages = serverMessage.Split('/'); string[] messages = serverMessage.Split('/');
for (int i = 0; i < messages.Length; i++) try
{ {
if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate for (int i = 0; i < messages.Length; i++)
{ {
if (messages[i].Contains(" ")) continue; // Spaces found, do not translate if (!IsServerMessageWithVariables(messages[i])) // No variables, try to translate
string msg = Get(messages[i], true);
if (msg != null) // If a translation was found, otherwise use the original
{ {
messages[i] = msg; if (messages[i].Contains(" ")) continue; // Spaces found, do not translate
} string msg = Get(messages[i], true);
} if (msg != null) // If a translation was found, otherwise use the original
else {
{ messages[i] = msg;
string[] messageWithVariables = messages[i].Split('~'); }
string msg = Get(messageWithVariables[0], true);
if (msg != null) // If a translation was found, otherwise use the original
{
messages[i] = msg;
} }
else else
{ {
continue; // No translation found, probably caused by player input -> skip variable handling string[] messageWithVariables = messages[i].Split('~');
} string msg = Get(messageWithVariables[0], true);
// First index is always the message identifier -> start at 1 if (msg != null) // If a translation was found, otherwise use the original
for (int j = 1; j < messageWithVariables.Length; j++) {
{ messages[i] = msg;
string[] variableAndValue = messageWithVariables[j].Split('='); }
messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]); else
{
continue; // No translation found, probably caused by player input -> skip variable handling
}
// First index is always the message identifier -> start at 1
for (int j = 1; j < messageWithVariables.Length; j++)
{
string[] variableAndValue = messageWithVariables[j].Split('=');
messages[i] = messages[i].Replace(variableAndValue[0], variableAndValue[1]);
}
} }
} }
string translatedServerMessage = string.Empty;
for (int i = 0; i < messages.Length; i++)
{
translatedServerMessage += messages[i];
}
return translatedServerMessage;
} }
string translatedServerMessage = string.Empty; catch (IndexOutOfRangeException exception)
for (int i = 0; i < messages.Length; i++)
{ {
translatedServerMessage += messages[i]; string errorMsg = "Failed to translate server message \"" + serverMessage + "\".";
#if DEBUG
DebugConsole.ThrowError(errorMsg, exception);
#endif
GameAnalyticsManager.AddErrorEventOnce("TextManager.GetServerMessage:" + serverMessage, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return errorMsg;
} }
return translatedServerMessage;
} }
public static bool IsServerMessageWithVariables(string message) public static bool IsServerMessageWithVariables(string message)
@@ -33,7 +33,11 @@ namespace Barotrauma
texts.Add(infoName, infoList); texts.Add(infoName, infoList);
} }
infoList.Add(subElement.ElementInnerText()); string text = subElement.ElementInnerText();
text = text.Replace("&amp;", "&");
text = text.Replace("&lt;", "<");
text = text.Replace("&gt;", ">");
infoList.Add(text);
} }
} }
Binary file not shown.
Binary file not shown.
Binary file not shown.

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