(a00338777) v0.9.2.1

This commit is contained in:
Joonas Rikkonen
2019-08-26 19:58:19 +03:00
parent 0f63da27b2
commit 80698b58b0
311 changed files with 11763 additions and 4507 deletions
@@ -40,6 +40,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\GameMain.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\GameMain.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\CrewManager.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\CrewManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameMode.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\CampaignMode.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\MultiPlayerCampaign.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\MultiPlayerCampaign.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\SinglePlayerCampaign.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\SinglePlayerCampaign.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\BasicTutorial.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\GameSession\GameModes\Tutorials\BasicTutorial.cs" />
@@ -165,6 +166,10 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetEntityEvent\NetEntityEvent.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetEntityEvent\NetEntityEvent.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetStats.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Networking\NetStats.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\OrderChatMessage.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Networking\OrderChatMessage.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\ClientPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\LidgrenClientPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\SteamP2PClientPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\Primitives\Peers\SteamP2POwnerPeer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\RespawnManager.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Networking\RespawnManager.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerInfo.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerInfo.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerLog.cs" /> <Compile Include="$(MSBuildThisFileDirectory)Source\Networking\ServerLog.cs" />
@@ -93,6 +93,9 @@
<Reference Include="nVLC, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="nVLC, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\Libraries\NuGet\nVLC.3.0.0\lib\net40\nVLC.dll</HintPath> <HintPath>..\..\Libraries\NuGet\nVLC.3.0.0\lib\net40\nVLC.dll</HintPath>
</Reference> </Reference>
<Reference Include="NVorbis, Version=0.8.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\Libraries\NuGet\NVorbis.0.8.6\lib\net35\NVorbis.dll</HintPath>
</Reference>
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>..\..\Libraries\NuGet\OpenTK.3.0.1\lib\net20\OpenTK.dll</HintPath> <HintPath>..\..\Libraries\NuGet\OpenTK.3.0.1\lib\net20\OpenTK.dll</HintPath>
</Reference> </Reference>
+6 -3
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">ReleaseMac</Configuration> <Configuration Condition=" '$(Configuration)' == '' ">ReleaseMac</Configuration>
@@ -91,6 +91,9 @@
<Reference Include="nVLC, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="nVLC, Version=3.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\Libraries\NuGet\nVLC.3.0.0\lib\net40\nVLC.dll</HintPath> <HintPath>..\..\Libraries\NuGet\nVLC.3.0.0\lib\net40\nVLC.dll</HintPath>
</Reference> </Reference>
<Reference Include="NVorbis, Version=0.8.6.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\Libraries\NuGet\NVorbis.0.8.6\lib\net35\NVorbis.dll</HintPath>
</Reference>
<Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL"> <Reference Include="OpenTK, Version=3.0.1.0, Culture=neutral, PublicKeyToken=bad199fe84eb3df4, processorArchitecture=MSIL">
<HintPath>..\..\Libraries\NuGet\OpenTK.3.0.1\lib\net20\OpenTK.dll</HintPath> <HintPath>..\..\Libraries\NuGet\OpenTK.3.0.1\lib\net20\OpenTK.dll</HintPath>
</Reference> </Reference>
@@ -316,8 +319,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup /> <ItemGroup />
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Icon.bmp"> <EmbeddedResource Include="Icon.bmp">
<LogicalName>Icon.bmp</LogicalName> <LogicalName>Icon.bmp</LogicalName>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<Import Project="ClientCode.projitems" Label="Shared" /> <Import Project="ClientCode.projitems" Label="Shared" />
@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers // You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below: // by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.9.200.0")] [assembly: AssemblyVersion("0.9.2.1")]
[assembly: AssemblyFileVersion("0.9.1.0")] [assembly: AssemblyFileVersion("0.9.2.1")]
+2 -2
View File
@@ -185,9 +185,9 @@ namespace Barotrauma
position += amount; position += amount;
} }
public void ClientWrite(NetOutgoingMessage msg) public void ClientWrite(IWriteMessage msg)
{ {
if (Character.Controlled != null && !Character.Controlled.IsDead) return; if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
msg.Write((byte)ClientNetObject.SPECTATING_POS); msg.Write((byte)ClientNetObject.SPECTATING_POS);
msg.Write(position.X); msg.Write(position.X);
@@ -138,7 +138,7 @@ namespace Barotrauma
brokenItemsCheckTimer = 1.0f; brokenItemsCheckTimer = 1.0f;
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (!item.Repairables.Any(r => item.Condition < r.ShowRepairUIThreshold)) { continue; } if (!item.Repairables.Any(r => item.ConditionPercentage < r.ShowRepairUIThreshold)) { continue; }
if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; } if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
Vector2 diff = item.WorldPosition - character.WorldPosition; Vector2 diff = item.WorldPosition - character.WorldPosition;
@@ -210,7 +210,7 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2);
textPos.Y += offset.Y; textPos.Y += offset.Y;
if (character.FocusedCharacter.CanInventoryBeAccessed) if (character.FocusedCharacter.CanBeDragged)
{ {
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBind(InputType.Grab).ToString()), GUI.DrawString(spriteBatch, textPos, GetCachedHudText("GrabHint", GameMain.Config.KeyBind(InputType.Grab).ToString()),
Color.LightGreen, Color.Black, 2, GUI.SmallFont); Color.LightGreen, Color.Black, 2, GUI.SmallFont);
@@ -1,5 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Lidgren.Network; using Barotrauma.Networking;
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
@@ -53,7 +53,7 @@ namespace Barotrauma
if (personalityTrait != null && TextManager.Language == "English") if (personalityTrait != null && TextManager.Language == "English")
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), personalityTrait.Name), font: font); TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + personalityTrait.Name.Replace(" ", ""))), font: font);
} }
//spacing //spacing
@@ -220,7 +220,7 @@ namespace Barotrauma
} }
public static CharacterInfo ClientRead(string configPath, NetBuffer inc) public static CharacterInfo ClientRead(string configPath, IReadMessage inc)
{ {
ushort infoID = inc.ReadUInt16(); ushort infoID = inc.ReadUInt16();
string newName = inc.ReadString(); string newName = inc.ReadString();
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Linq; using System.Linq;
@@ -22,12 +21,12 @@ namespace Barotrauma
} }
//freeze AI characters if more than 1 seconds have passed since last update from the server //freeze AI characters if more than 1 seconds have passed since last update from the server
if (lastRecvPositionUpdateTime < NetTime.Now - 1.0f) if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - 1.0f)
{ {
AnimController.Frozen = true; AnimController.Frozen = true;
memState.Clear(); memState.Clear();
//hide after 2 seconds //hide after 2 seconds
if (lastRecvPositionUpdateTime < NetTime.Now - 2.0f) if (lastRecvPositionUpdateTime < Lidgren.Network.NetTime.Now - 2.0f)
{ {
Enabled = false; Enabled = false;
return; return;
@@ -99,22 +98,22 @@ namespace Barotrauma
} }
} }
public virtual void ClientWrite(NetBuffer msg, object[] extraData = null) public virtual void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
if (extraData != null) if (extraData != null)
{ {
switch ((NetEntityEvent.Type)extraData[0]) switch ((NetEntityEvent.Type)extraData[0])
{ {
case NetEntityEvent.Type.InventoryState: case NetEntityEvent.Type.InventoryState:
msg.WriteRangedInteger(0, 3, 0); msg.WriteRangedIntegerDeprecated(0, 3, 0);
Inventory.ClientWrite(msg, extraData); Inventory.ClientWrite(msg, extraData);
break; break;
case NetEntityEvent.Type.Treatment: case NetEntityEvent.Type.Treatment:
msg.WriteRangedInteger(0, 3, 1); msg.WriteRangedIntegerDeprecated(0, 3, 1);
msg.Write(AnimController.Anim == AnimController.Animation.CPR); msg.Write(AnimController.Anim == AnimController.Animation.CPR);
break; break;
case NetEntityEvent.Type.Status: case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(0, 3, 2); msg.WriteRangedIntegerDeprecated(0, 3, 2);
break; break;
} }
} }
@@ -132,7 +131,7 @@ namespace Barotrauma
msg.Write(inputCount); msg.Write(inputCount);
for (int i = 0; i < inputCount; i++) for (int i = 0; i < inputCount; i++)
{ {
msg.WriteRangedInteger(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states); msg.WriteRangedIntegerDeprecated(0, (int)InputNetFlags.MaxVal, (int)memInput[i].states);
msg.Write(memInput[i].intAim); msg.Write(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) || if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Deselect) || memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
@@ -147,14 +146,14 @@ namespace Barotrauma
msg.WritePadBits(); msg.WritePadBits();
} }
public virtual void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public virtual void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
switch (type) switch (type)
{ {
case ServerNetObject.ENTITY_POSITION: case ServerNetObject.ENTITY_POSITION:
bool facingRight = AnimController.Dir > 0.0f; bool facingRight = AnimController.Dir > 0.0f;
lastRecvPositionUpdateTime = (float)NetTime.Now; lastRecvPositionUpdateTime = (float)Lidgren.Network.NetTime.Now;
AnimController.Frozen = false; AnimController.Frozen = false;
Enabled = true; Enabled = true;
@@ -222,8 +221,8 @@ namespace Barotrauma
} }
Vector2 pos = new Vector2( Vector2 pos = new Vector2(
msg.ReadFloat(), msg.ReadSingle(),
msg.ReadFloat()); msg.ReadSingle());
float MaxVel = NetConfig.MaxPhysicsBodyVelocity; float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
Vector2 linearVelocity = new Vector2( Vector2 linearVelocity = new Vector2(
msg.ReadRangedSingle(-MaxVel, MaxVel, 12), msg.ReadRangedSingle(-MaxVel, MaxVel, 12),
@@ -235,7 +234,7 @@ namespace Barotrauma
float? angularVelocity = null; float? angularVelocity = null;
if (!fixedRotation) if (!fixedRotation)
{ {
rotation = msg.ReadFloat(); rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8); angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8); angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
@@ -336,7 +335,7 @@ namespace Barotrauma
} }
} }
public static Character ReadSpawnData(NetBuffer inc, bool spawn = true) public static Character ReadSpawnData(IReadMessage inc, bool spawn = true)
{ {
DebugConsole.NewMessage("READING CHARACTER SPAWN DATA", Color.Cyan); DebugConsole.NewMessage("READING CHARACTER SPAWN DATA", Color.Cyan);
@@ -347,7 +346,7 @@ namespace Barotrauma
string speciesName = inc.ReadString(); string speciesName = inc.ReadString();
string seed = inc.ReadString(); string seed = inc.ReadString();
Vector2 position = new Vector2(inc.ReadFloat(), inc.ReadFloat()); Vector2 position = new Vector2(inc.ReadSingle(), inc.ReadSingle());
bool enabled = inc.ReadBoolean(); bool enabled = inc.ReadBoolean();
@@ -414,8 +413,17 @@ namespace Barotrauma
return character; return character;
} }
private void ReadTraitorStatus(IReadMessage msg)
{
IsTraitor = msg.ReadBoolean();
if (IsTraitor)
{
TraitorCurrentObjective = msg.ReadString();
}
}
private void ReadStatus(NetBuffer msg) private void ReadStatus(IReadMessage msg)
{ {
bool isDead = msg.ReadBoolean(); bool isDead = msg.ReadBoolean();
if (isDead) if (isDead)
@@ -450,7 +458,7 @@ namespace Barotrauma
else else
{ {
if (IsDead) Revive(); if (IsDead) Revive();
CharacterHealth.ClientRead(msg); CharacterHealth.ClientRead(msg);
} }
} }
@@ -1,6 +1,5 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -1375,7 +1374,7 @@ namespace Barotrauma
(int)(limbHealth.HighlightArea.Height * scale)); (int)(limbHealth.HighlightArea.Height * scale));
} }
public void ClientRead(NetBuffer inc) public void ClientRead(IReadMessage inc)
{ {
List<Pair<AfflictionPrefab, float>> newAfflictions = new List<Pair<AfflictionPrefab, float>>(); List<Pair<AfflictionPrefab, float>> newAfflictions = new List<Pair<AfflictionPrefab, float>>();
@@ -441,7 +441,7 @@ namespace Barotrauma
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
if (herpesStrength > 0.0f) if (herpesStrength > 0.0f)
{ {
DrawWearable(HerpesSprite, depthStep, spriteBatch, color * (herpesStrength / 100.0f), spriteEffect); DrawWearable(HerpesSprite, depthStep, spriteBatch, color * Math.Min(herpesStrength / 10.0f, 1.0f), spriteEffect);
depthStep += 0.000001f; depthStep += 0.000001f;
} }
} }
@@ -199,6 +199,7 @@ namespace Barotrauma
return client.HasPermission(ClientPermissions.Kick); return client.HasPermission(ClientPermissions.Kick);
case "ban": case "ban":
case "banip": case "banip":
case "banendpoint":
return client.HasPermission(ClientPermissions.Ban); return client.HasPermission(ClientPermissions.Ban);
case "unban": case "unban":
case "unbanip": case "unbanip":
@@ -317,7 +318,13 @@ namespace Barotrauma
private static void AssignRelayToServer(string names, bool relay) private static void AssignRelayToServer(string names, bool relay)
{ {
commands.First(c => c.names.Intersect(names.Split('|')).Count() > 0).RelayToServer = relay; Command command = commands.Find(c => c.names.Intersect(names.Split('|')).Count() > 0);
if (command == null)
{
DebugConsole.Log("Could not assign to relay to server: " + names);
return;
}
command.RelayToServer = relay;
} }
private static void InitProjectSpecific() private static void InitProjectSpecific()
@@ -358,16 +365,23 @@ namespace Barotrauma
} }
})); }));
commands.Add(new Command("startclient", "", (string[] args) => commands.Add(new Command("startlidgrenclient", "", (string[] args) =>
{ {
if (args.Length == 0) return; if (args.Length == 0) return;
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
GameMain.Client = new GameClient("Name", args[0]); GameMain.Client = new GameClient("Name", args[0], 0);
} }
})); }));
commands.Add(new Command("startsteamp2pclient", "", (string[] args) =>
{
if (GameMain.Client == null)
{
GameMain.Client = new GameClient("Name", null, 76561198977850505); //this is juan's alt account, feel free to abuse this one
}
}));
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) => commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
{ {
@@ -465,10 +479,16 @@ namespace Barotrauma
commands.Add(new Command("clientlist", "", (string[] args) => { })); commands.Add(new Command("clientlist", "", (string[] args) => { }));
AssignRelayToServer("clientlist", true); AssignRelayToServer("clientlist", true);
commands.Add(new Command("say", "", (string[] args) => { }));
AssignRelayToServer("say", true);
commands.Add(new Command("msg", "", (string[] args) => { }));
AssignRelayToServer("msg", true);
commands.Add(new Command("setmaxplayers|maxplayers", "", (string[] args) => { })); commands.Add(new Command("setmaxplayers|maxplayers", "", (string[] args) => { }));
AssignRelayToServer("setmaxplayers", true); AssignRelayToServer("setmaxplayers", true);
commands.Add(new Command("setpassword|password", "", (string[] args) => { })); commands.Add(new Command("setpassword|password", "", (string[] args) => { }));
AssignRelayToServer("setpassword", true); AssignRelayToServer("setpassword", true);
commands.Add(new Command("traitorlist", "", (string[] args) => { }));
AssignRelayToServer("traitorlist", true);
AssignOnExecute("control", (string[] args) => AssignOnExecute("control", (string[] args) =>
{ {
@@ -551,6 +571,7 @@ namespace Barotrauma
if (args.Length < 3) if (args.Length < 3)
{ {
ThrowError("Not enough arguments provided! At least three required."); ThrowError("Not enough arguments provided! At least three required.");
return;
} }
if (!byte.TryParse(args[0], out byte r)) if (!byte.TryParse(args[0], out byte r))
{ {
@@ -952,7 +973,36 @@ namespace Barotrauma
} }
}, isCheat: false)); }, isCheat: false));
commands.Add(new Command("setentityproperties", "setentityproperties [property name] [value]: Sets the value of some property on all selected items/structures in the sub editor.", (string[] args) =>
{
if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) { return; }
foreach (MapEntity me in MapEntity.SelectedList)
{
if (me is ISerializableEntity serializableEntity)
{
if (serializableEntity.SerializableProperties == null)
{
continue;
}
if (!serializableEntity.SerializableProperties.TryGetValue(args[0].ToLowerInvariant(), out SerializableProperty property))
{
NewMessage("Property \"" + args[0] + "\" not found in the entity \"" + me.ToString() + "\".", Color.Orange);
continue;
}
if (!property.TrySetValue(me, args[1]))
{
NewMessage("Failed to set the value of \"" + args[0] + "\" to \"" + args[1] + "\" on the entity \"" + me.ToString() + "\".", Color.Orange);
}
}
}
}));
#if DEBUG #if DEBUG
commands.Add(new Command("printreceivertransfers", "", (string[] args) =>
{
GameMain.Client.PrintReceiverTransters();
}));
commands.Add(new Command("checkmissingloca", "", (string[] args) => commands.Add(new Command("checkmissingloca", "", (string[] args) =>
{ {
foreach (MapEntityPrefab me in MapEntityPrefab.List) foreach (MapEntityPrefab me in MapEntityPrefab.List)
@@ -1201,6 +1251,8 @@ namespace Barotrauma
{ {
GameMain.Config.MusicVolume = 0.5f; GameMain.Config.MusicVolume = 0.5f;
GameMain.Config.SoundVolume = 0.5f; GameMain.Config.SoundVolume = 0.5f;
GameMain.Config.DynamicRangeCompressionEnabled = true;
GameMain.Config.VoipAttenuationEnabled = true;
NewMessage("Music and sound volume set to 0.5", Color.Green); NewMessage("Music and sound volume set to 0.5", Color.Green);
GameMain.Config.GraphicsWidth = 0; GameMain.Config.GraphicsWidth = 0;
@@ -1416,11 +1468,11 @@ namespace Barotrauma
); );
AssignOnClientExecute( AssignOnClientExecute(
"banip", "banendpoint|banip",
(string[] args) => (string[] args) =>
{ {
if (GameMain.Client == null || args.Length == 0) return; if (GameMain.Client == null || args.Length == 0) return;
ShowQuestionPrompt("Reason for banning the ip \"" + args[0] + "\"?", (reason) => ShowQuestionPrompt("Reason for banning the endpoint \"" + args[0] + "\"?", (reason) =>
{ {
ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) => ShowQuestionPrompt("Enter the duration of the ban (leave empty to ban permanently, or use the format \"[days] d [hours] h\")", (duration) =>
{ {
@@ -1436,7 +1488,7 @@ namespace Barotrauma
} }
GameMain.Client.SendConsoleCommand( GameMain.Client.SendConsoleCommand(
"banip " + "banendpoint " +
args[0] + " " + args[0] + " " +
(banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " + (banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " +
reason); reason);
@@ -1779,6 +1831,21 @@ namespace Barotrauma
character.ReloadHead(id, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); character.ReloadHead(id, hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
} }
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("spawnsub", "spawnsub [subname]: Spawn a submarine at the position of the cursor", (string[] args) =>
{
try
{
Submarine spawnedSub = Submarine.Load(args[0], false);
spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
}
catch (Exception e)
{
string errorMsg = "Failed to spawn a submarine. Arguments: \"" + string.Join(" ", args) + "\".";
ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnSubmarine:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace);
}
}, isCheat: true));
} }
private static void ReloadWearables(Character character, int variant = 0) private static void ReloadWearables(Character character, int variant = 0)
@@ -4,16 +4,13 @@ namespace Barotrauma
{ {
partial class Mission partial class Mission
{ {
public void ShowMessage(int index) partial void ShowMessageProjSpecific(int index)
{ {
if (index >= Headers.Count && index >= Messages.Count) return; if (index >= Headers.Count && index >= Messages.Count) return;
string header = index < Headers.Count ? Headers[index] : ""; string header = index < Headers.Count ? Headers[index] : "";
string message = index < Messages.Count ? Messages[index] : ""; string message = index < Messages.Count ? Messages[index] : "";
//TODO: reimplement
//GameServer.Log(TextManager.Get("MissionInfo") + ": " + header + " - " + message, ServerLog.MessageType.ServerMessage);
new GUIMessageBox(header, message); new GUIMessageBox(header, message);
} }
} }
@@ -5,19 +5,14 @@ namespace Barotrauma
{ {
partial class MissionMode : GameMode partial class MissionMode : GameMode
{ {
public override void MsgBox() public override void ShowStartMessage()
{ {
if (mission == null) return; if (mission == null) return;
var missionMsg = new GUIMessageBox(mission.Name, mission.Description, new Vector2(0.25f, 0.0f), new Point(400, 200)) new GUIMessageBox(mission.Name, mission.Description, new Vector2(0.25f, 0.0f), new Point(400, 200))
{ {
UserData = "missionstartmessage" UserData = "missionstartmessage"
}; };
#if SERVER
Networking.GameServer.Log(TextManager.Get("Mission") + ": " + mission.Name, Networking.ServerLog.MessageType.ServerMessage);
Networking.GameServer.Log(mission.Description, Networking.ServerLog.MessageType.ServerMessage);
#endif
} }
} }
} }
+24 -2
View File
@@ -310,6 +310,16 @@ namespace Barotrauma
"Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, SmallFont); "Sounds (Ctrl+S to hide): ", Color.White, Color.Black * 0.5f, 0, SmallFont);
y += 15; y += 15;
DrawString(spriteBatch, new Vector2(500, y),
"Current playback amplitude: " + GameMain.SoundManager.PlaybackAmplitude.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont);
y += 15;
DrawString(spriteBatch, new Vector2(500, y),
"Compressed dynamic range gain: " + GameMain.SoundManager.CompressionDynamicRangeGain.ToString(), Color.White, Color.Black * 0.5f, 0, SmallFont);
y += 15;
DrawString(spriteBatch, new Vector2(500, y), DrawString(spriteBatch, new Vector2(500, y),
"Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, SmallFont); "Loaded sounds: " + GameMain.SoundManager.LoadedSoundCount + " (" + GameMain.SoundManager.UniqueLoadedSoundCount + " unique)", Color.White, Color.Black * 0.5f, 0, SmallFont);
y += 15; y += 15;
@@ -343,18 +353,21 @@ namespace Barotrauma
soundStr += " (looping)"; soundStr += " (looping)";
clr = Color.Yellow; clr = Color.Yellow;
} }
if (playingSoundChannel.IsStream) if (playingSoundChannel.IsStream)
{ {
soundStr += " (streaming)"; soundStr += " (streaming)";
clr = Color.Lime; clr = Color.Lime;
} }
if (!playingSoundChannel.IsPlaying) if (!playingSoundChannel.IsPlaying)
{ {
soundStr += " (stopped)"; soundStr += " (stopped)";
clr *= 0.5f; clr *= 0.5f;
} }
else if (playingSoundChannel.Muffled)
{
soundStr += " (muffled)";
clr = Color.Lerp(clr, Color.LightGray, 0.5f);
}
} }
DrawString(spriteBatch, new Vector2(500, y), soundStr, clr, Color.Black * 0.5f, 0, SmallFont); DrawString(spriteBatch, new Vector2(500, y), soundStr, clr, Color.Black * 0.5f, 0, SmallFont);
@@ -640,6 +653,12 @@ namespace Barotrauma
return MouseOn; return MouseOn;
} }
public static bool HasSizeChanged(Point referenceResolution, float referenceUIScale, float referenceHUDScale)
{
return GameMain.GraphicsWidth != referenceResolution.X || GameMain.GraphicsHeight != referenceResolution.Y ||
referenceUIScale != Inventory.UIScale || referenceHUDScale != Scale;
}
public static void Update(float deltaTime) public static void Update(float deltaTime)
{ {
HandlePersistingElements(deltaTime); HandlePersistingElements(deltaTime);
@@ -1461,6 +1480,9 @@ namespace Barotrauma
if (pauseMenuOpen) if (pauseMenuOpen)
{ {
Inventory.draggingItem = null;
Inventory.DraggingInventory = null;
PauseMenu = new GUIFrame(new RectTransform(Vector2.One, Canvas), style: null, color: Color.Black * 0.5f); PauseMenu = new GUIFrame(new RectTransform(Vector2.One, Canvas), style: null, color: Color.Black * 0.5f);
var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.35f), PauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(200, 300) }); var pauseMenuInner = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.35f), PauseMenu.RectTransform, Anchor.Center) { MinSize = new Point(200, 300) });
@@ -67,6 +67,21 @@ namespace Barotrauma
} }
// TODO: refactor? // TODO: refactor?
public GUIComponent FindChild(Func<GUIComponent, bool> predicate, bool recursive = false)
{
var matchingChild = Children.FirstOrDefault(predicate);
if (recursive && matchingChild == null)
{
foreach (GUIComponent child in Children)
{
matchingChild = child.FindChild(predicate, recursive);
if (matchingChild != null) return matchingChild;
}
}
return matchingChild;
}
public GUIComponent FindChild(object userData, bool recursive = false) public GUIComponent FindChild(object userData, bool recursive = false)
{ {
var matchingChild = Children.FirstOrDefault(c => c.userData == userData); var matchingChild = Children.FirstOrDefault(c => c.userData == userData);
@@ -1,11 +1,13 @@
using Microsoft.Xna.Framework; using EventInput;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
public class GUIDropDown : GUIComponent public class GUIDropDown : GUIComponent, IKeyboardSubscriber
{ {
public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null); public delegate bool OnSelectedHandler(GUIComponent selected, object obj = null);
public OnSelectedHandler OnSelected; public OnSelectedHandler OnSelected;
@@ -42,10 +44,22 @@ namespace Barotrauma
set { button.Enabled = value; } set { button.Enabled = value; }
} }
public GUIComponent Selected public GUIComponent SelectedComponent
{ {
get { return listBox.SelectedComponent; } get { return listBox.SelectedComponent; }
} }
public bool Selected
{
get
{
return Dropped;
}
set
{
Dropped = value;
}
}
public GUIListBox ListBox public GUIListBox ListBox
{ {
@@ -69,6 +83,30 @@ namespace Barotrauma
} }
} }
public void ReceiveTextInput(char inputChar)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
public void ReceiveTextInput(string text) { }
public void ReceiveCommandInput(char command) { }
public void ReceiveSpecialInput(Keys key)
{
switch (key)
{
case Keys.Up:
case Keys.Down:
listBox.ReceiveSpecialInput(key);
GUI.KeyboardDispatcher.Subscriber = this;
break;
case Keys.Enter:
case Keys.Space:
case Keys.Escape:
GUI.KeyboardDispatcher.Subscriber = null;
break;
}
}
private List<object> selectedDataMultiple = new List<object>(); private List<object> selectedDataMultiple = new List<object>();
public IEnumerable<object> SelectedDataMultiple public IEnumerable<object> SelectedDataMultiple
{ {
@@ -287,6 +325,12 @@ namespace Barotrauma
{ {
OnDropped?.Invoke(this, userData); OnDropped?.Invoke(this, userData);
listBox.UpdateScrollBarSize(); listBox.UpdateScrollBarSize();
GUI.KeyboardDispatcher.Subscriber = this;
}
else if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
} }
return true; return true;
} }
@@ -343,6 +387,10 @@ namespace Barotrauma
if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition)) if (!listBoxRect.Contains(PlayerInput.MousePosition) && !button.Rect.Contains(PlayerInput.MousePosition))
{ {
Dropped = false; Dropped = false;
if (GUI.KeyboardDispatcher.Subscriber == this)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
} }
} }
} }
@@ -651,7 +651,9 @@ namespace Barotrauma
case Keys.Up: case Keys.Up:
SelectPrevious(); SelectPrevious();
break; break;
default: case Keys.Enter:
case Keys.Space:
case Keys.Escape:
GUI.KeyboardDispatcher.Subscriber = null; GUI.KeyboardDispatcher.Subscriber = null;
break; break;
} }
@@ -26,6 +26,7 @@ namespace Barotrauma
public TextGetterHandler TextGetter; public TextGetterHandler TextGetter;
public bool Wrap; public bool Wrap;
private bool playerInput;
public bool RoundToNearestPixel = true; public bool RoundToNearestPixel = true;
@@ -193,7 +194,7 @@ namespace Barotrauma
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
/// </summary> /// </summary>
public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null, public GUITextBlock(RectTransform rectT, string text, Color? textColor = null, ScalableFont font = null,
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null) Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
: base(style, rectT) : base(style, rectT)
{ {
if (color.HasValue) if (color.HasValue)
@@ -208,6 +209,7 @@ namespace Barotrauma
this.textAlignment = textAlignment; this.textAlignment = textAlignment;
this.Wrap = wrap; this.Wrap = wrap;
this.Text = text ?? ""; this.Text = text ?? "";
this.playerInput = playerInput;
if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text)) if (rectT.Rect.Height == 0 && !string.IsNullOrEmpty(text))
{ {
CalculateHeightFromText(); CalculateHeightFromText();
@@ -254,7 +256,7 @@ namespace Barotrauma
if (Wrap && rect.Width > 0) if (Wrap && rect.Width > 0)
{ {
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale); wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale, playerInput);
TextSize = MeasureText(wrappedText); TextSize = MeasureText(wrappedText);
} }
else if (OverflowClip) else if (OverflowClip)
@@ -199,6 +199,12 @@ namespace Barotrauma
} }
} }
public Vector4 Padding
{
get { return textBlock.Padding; }
set { textBlock.Padding = value; }
}
// TODO: should this be defined in the stylesheet? // TODO: should this be defined in the stylesheet?
public Color SelectionColor { get; set; } = Color.White * 0.25f; public Color SelectionColor { get; set; } = Color.White * 0.25f;
@@ -229,7 +235,7 @@ namespace Barotrauma
this.color = color ?? Color.White; this.color = color ?? Color.White;
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color); frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
GUI.Style.Apply(frame, style == "" ? "GUITextBox" : style); GUI.Style.Apply(frame, style == "" ? "GUITextBox" : style);
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.Center), text, textColor, font, textAlignment, wrap); textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.Center), text, textColor, font, textAlignment, wrap, playerInput: true);
GUI.Style.Apply(textBlock, "", this); GUI.Style.Apply(textBlock, "", this);
CaretEnabled = true; CaretEnabled = true;
caretPosDirty = true; caretPosDirty = true;
@@ -308,6 +314,7 @@ namespace Barotrauma
protected List<Tuple<Vector2, int>> GetAllPositions() protected List<Tuple<Vector2, int>> GetAllPositions()
{ {
float halfHeight = Font.MeasureString("T").Y * 0.5f;
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText; string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
var positions = new List<Tuple<Vector2, int>>(); var positions = new List<Tuple<Vector2, int>>();
if (textDrawn.Contains("\n")) if (textDrawn.Contains("\n"))
@@ -323,9 +330,9 @@ namespace Barotrauma
for (int j = 0; j <= line.Length; j++) for (int j = 0; j <= line.Length; j++)
{ {
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j)); Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j));
Vector2 indexPos = new Vector2(lineTextSize.X + textBlock.Padding.X, totalTextHeight + textBlock.Padding.Y); Vector2 indexPos = new Vector2(lineTextSize.X + textBlock.Padding.X, totalTextHeight + textBlock.Padding.Y - halfHeight);
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue); //DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(textBlock.Rect.Location.ToVector2() + indexPos, index + j)); positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
} }
index = totalIndex; index = totalIndex;
} }
@@ -336,9 +343,9 @@ namespace Barotrauma
for (int i = 0; i <= textBlock.Text.Length; i++) for (int i = 0; i <= textBlock.Text.Length; i++)
{ {
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i)); Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i));
Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y) + textBlock.TextPos - textBlock.Origin; Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y - halfHeight) + textBlock.TextPos - textBlock.Origin;
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke); //DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(textBlock.Rect.Location.ToVector2() + indexPos, i)); positions.Add(new Tuple<Vector2, int>(indexPos, i));
} }
} }
return positions; return positions;
@@ -346,10 +353,64 @@ namespace Barotrauma
public int GetCaretIndexFromScreenPos(Vector2 pos) public int GetCaretIndexFromScreenPos(Vector2 pos)
{ {
var positions = GetAllPositions().OrderBy(p => Vector2.DistanceSquared(p.Item1, pos)); return GetCaretIndexFromLocalPos(pos - textBlock.Rect.Location.ToVector2());
var posIndex = positions.FirstOrDefault(); }
public int GetCaretIndexFromLocalPos(Vector2 pos)
{
var positions = GetAllPositions();
if (positions.Count==0) { return 0; }
float halfHeight = Font.MeasureString("T").Y * 0.5f;
var currPosition = positions[0];
for (int i=1;i<positions.Count;i++)
{
var p1 = positions[i];
var p2 = currPosition;
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
if (diffY < -3.0f)
{
currPosition = p1; continue;
}
else if (diffY > 3.0f)
{
continue;
}
else
{
diffY = Math.Abs(p1.Item1.Y - pos.Y);
if (diffY < halfHeight)
{
//we are on this line, select the nearest character
float diffX = Math.Abs(p1.Item1.X - pos.X) - Math.Abs(p2.Item1.X - pos.X);
if (diffX < -1.0f)
{
currPosition = p1; continue;
}
else
{
continue;
}
}
else
{
//we are on a different line, preserve order
if (p1.Item2 < p2.Item2)
{
if (p1.Item1.Y > pos.Y) { currPosition = p1; }
}
else if (p1.Item2 > p2.Item2)
{
if (p1.Item1.Y < pos.Y) { currPosition = p1; }
}
continue;
}
}
}
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke); //GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
return posIndex != null ? posIndex.Item2 : textBlock.Text.Length; return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
} }
public void Select() public void Select()
@@ -674,9 +735,9 @@ namespace Barotrauma
{ {
InitSelectionStart(); InitSelectionStart();
} }
float lineHeight = Font.MeasureString(Text).Y; float lineHeight = Font.MeasureString("T").Y;
int newIndex = GetCaretIndexFromScreenPos(new Vector2(CaretScreenPos.X, CaretScreenPos.Y - lineHeight / 2)); int newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y-lineHeight));
CaretIndex = newIndex != CaretIndex ? newIndex : 0; CaretIndex = newIndex;
caretTimer = 0; caretTimer = 0;
HandleSelection(); HandleSelection();
break; break;
@@ -685,9 +746,9 @@ namespace Barotrauma
{ {
InitSelectionStart(); InitSelectionStart();
} }
lineHeight = Font.MeasureString(Text).Y; lineHeight = Font.MeasureString("T").Y;
newIndex = GetCaretIndexFromScreenPos(new Vector2(CaretScreenPos.X, CaretScreenPos.Y + lineHeight * 2)); newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y+lineHeight));
CaretIndex = newIndex != CaretIndex ? newIndex : Text.Length; CaretIndex = newIndex;
caretTimer = 0; caretTimer = 0;
HandleSelection(); HandleSelection();
break; break;
@@ -114,10 +114,13 @@ namespace Barotrauma
if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.LeftButtonHeld())) if (IsMouseOver || (!RequireMouseOn && selectedWidgets.Contains(this) && PlayerInput.LeftButtonHeld()))
{ {
Hovered?.Invoke(); Hovered?.Invoke();
if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None()) if (RequireMouseOn || PlayerInput.LeftButtonDown())
{ {
selectedWidgets.Add(this); if ((multiselect && !selectedWidgets.Contains(this)) || selectedWidgets.None())
Selected?.Invoke(); {
selectedWidgets.Add(this);
Selected?.Invoke();
}
} }
} }
else if (selectedWidgets.Contains(this)) else if (selectedWidgets.Contains(this))
+72 -9
View File
@@ -101,6 +101,10 @@ namespace Barotrauma
private GameTime fixedTime; private GameTime fixedTime;
public string ConnectName;
public string ConnectEndpoint;
public UInt64 ConnectLobby;
private static SpriteBatch spriteBatch; private static SpriteBatch spriteBatch;
private Viewport defaultViewport; private Viewport defaultViewport;
@@ -175,6 +179,12 @@ namespace Barotrauma
ConsoleArguments = args; ConsoleArguments = args;
ConnectName = null;
ConnectEndpoint = null;
ConnectLobby = 0;
ToolBox.ParseConnectCommand(ConsoleArguments, out ConnectName, out ConnectEndpoint, out ConnectLobby);
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window); GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
PerformanceCounter = new PerformanceCounter(); PerformanceCounter = new PerformanceCounter();
@@ -355,13 +365,13 @@ namespace Barotrauma
} }
SoundManager = new Sounds.SoundManager(); SoundManager = new Sounds.SoundManager();
SoundManager.SetCategoryGainMultiplier("default", Config.SoundVolume); SoundManager.SetCategoryGainMultiplier("default", Config.SoundVolume, 0);
SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume); SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume, 0);
SoundManager.SetCategoryGainMultiplier("waterambience", Config.SoundVolume); SoundManager.SetCategoryGainMultiplier("waterambience", Config.SoundVolume, 0);
SoundManager.SetCategoryGainMultiplier("music", Config.MusicVolume); SoundManager.SetCategoryGainMultiplier("music", Config.MusicVolume, 0);
SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume * 20.0f); SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume * 20.0f, 0);
if (ConsoleArguments.Contains("skipintro")) { if (ConsoleArguments.Contains("-skipintro")) {
Config.EnableSplashScreen = false; Config.EnableSplashScreen = false;
} }
@@ -390,7 +400,7 @@ namespace Barotrauma
{ {
if (SteamManager.AutoUpdateWorkshopItems()) if (SteamManager.AutoUpdateWorkshopItems())
{ {
ContentPackage.LoadAll(ContentPackage.Folder); ContentPackage.LoadAll();
Config.ReloadContentPackages(); Config.ReloadContentPackages();
} }
} }
@@ -494,7 +504,12 @@ namespace Barotrauma
if (SteamManager.USE_STEAM) if (SteamManager.USE_STEAM)
{ {
SteamWorkshopScreen = new SteamWorkshopScreen(); SteamWorkshopScreen = new SteamWorkshopScreen();
if (SteamManager.IsInitialized)
{
SteamManager.Instance.Friends.OnInvitedToGame += OnInvitedToGame;
SteamManager.Instance.Lobby.OnLobbyJoinRequested += OnLobbyJoinRequested;
}
} }
SubEditorScreen = new SubEditorScreen(); SubEditorScreen = new SubEditorScreen();
@@ -594,6 +609,18 @@ namespace Barotrauma
} }
} }
public void OnInvitedToGame(Facepunch.Steamworks.SteamFriend friend, string connectCommand)
{
ToolBox.ParseConnectCommand(connectCommand.Split(' '), out ConnectName, out ConnectEndpoint, out ConnectLobby);
DebugConsole.NewMessage(ConnectName+", "+ConnectEndpoint,Color.Yellow);
}
public void OnLobbyJoinRequested(UInt64 lobbyId)
{
SteamManager.JoinLobby(lobbyId, true);
}
/// <summary> /// <summary>
/// Allows the game to run logic such as updating the world, /// Allows the game to run logic such as updating the world,
/// checking for collisions, gathering input, and playing audio. /// checking for collisions, gathering input, and playing audio.
@@ -622,7 +649,7 @@ namespace Barotrauma
{ {
if (WindowActive || !Config.MuteOnFocusLost) if (WindowActive || !Config.MuteOnFocusLost)
{ {
SoundManager.ListenerGain = 1.0f; SoundManager.ListenerGain = SoundManager.CompressionDynamicRangeGain;
} }
else else
{ {
@@ -674,6 +701,40 @@ namespace Barotrauma
} }
else if (hasLoaded) else if (hasLoaded)
{ {
if (ConnectLobby != 0)
{
if (Client != null)
{
Client.Disconnect();
Client = null;
GameMain.MainMenuScreen.Select();
}
Steam.SteamManager.JoinLobby(ConnectLobby, true);
ConnectLobby = 0;
ConnectEndpoint = null;
ConnectName = null;
}
else if (!string.IsNullOrWhiteSpace(ConnectEndpoint))
{
if (Client != null)
{
Client.Disconnect();
Client = null;
GameMain.MainMenuScreen.Select();
}
UInt64 serverSteamId = SteamManager.SteamIDStringToUInt64(ConnectEndpoint);
Client = new GameClient(SteamManager.GetUsername(),
serverSteamId != 0 ? null : ConnectEndpoint,
serverSteamId,
string.IsNullOrWhiteSpace(ConnectName) ? ConnectEndpoint : ConnectName);
ConnectLobby = 0;
ConnectEndpoint = null;
ConnectName = null;
}
SoundPlayer.Update((float)Timing.Step); SoundPlayer.Update((float)Timing.Step);
if (PlayerInput.KeyHit(Keys.Escape) && WindowActive) if (PlayerInput.KeyHit(Keys.Escape) && WindowActive)
@@ -754,6 +815,8 @@ namespace Barotrauma
SteamManager.Update((float)Timing.Step); SteamManager.Update((float)Timing.Step);
SoundManager?.Update();
Timing.Accumulator -= Timing.Step; Timing.Accumulator -= Timing.Step;
sw.Stop(); sw.Stop();
@@ -1251,6 +1251,7 @@ namespace Barotrauma
if (!hoverArea.Contains(PlayerInput.MousePosition) || PlayerInput.RightButtonClicked()) if (!hoverArea.Contains(PlayerInput.MousePosition) || PlayerInput.RightButtonClicked())
{ {
orderTargetFrame = null; orderTargetFrame = null;
OrderOptionButtons.Clear();
} }
} }
} }
@@ -1361,7 +1362,9 @@ namespace Barotrauma
{ {
reportButtonFrame.Visible = true; reportButtonFrame.Visible = true;
var reportButtonParent = ChatBox ?? GameMain.Client.ChatBox; var reportButtonParent = ChatBox ?? GameMain.Client?.ChatBox;
if (reportButtonParent == null) { return; }
reportButtonFrame.RectTransform.AbsoluteOffset = new Point( reportButtonFrame.RectTransform.AbsoluteOffset = new Point(
Math.Min(reportButtonParent.GUIFrame.Rect.X, reportButtonParent.ToggleButton.Rect.X) - reportButtonFrame.Rect.Width - (int)(10 * GUI.Scale), Math.Min(reportButtonParent.GUIFrame.Rect.X, reportButtonParent.ToggleButton.Rect.X) - reportButtonFrame.Rect.Width - (int)(10 * GUI.Scale),
reportButtonParent.GUIFrame.Rect.Y); reportButtonParent.GUIFrame.Rect.Y);
@@ -0,0 +1,18 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
namespace Barotrauma
{
abstract partial class CampaignMode : GameMode
{
public override void ShowStartMessage()
{
if (Mission == null) return;
new GUIMessageBox(Mission.Name, Mission.Description, new Vector2(0.25f, 0.0f), new Point(400, 200))
{
UserData = "missionstartmessage"
};
}
}
}
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -10,6 +9,8 @@ namespace Barotrauma
{ {
partial class MultiPlayerCampaign : CampaignMode partial class MultiPlayerCampaign : CampaignMode
{ {
public bool SuppressStateSending = false;
private UInt16 startWatchmanID, endWatchmanID; private UInt16 startWatchmanID, endWatchmanID;
public static GUIComponent StartCampaignSetup( IEnumerable<Submarine> submarines, IEnumerable<string> saveFiles) public static GUIComponent StartCampaignSetup( IEnumerable<Submarine> submarines, IEnumerable<string> saveFiles)
@@ -129,7 +130,7 @@ namespace Barotrauma
} }
} }
public void ClientWrite(NetBuffer msg) public void ClientWrite(IWriteMessage msg)
{ {
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
@@ -147,7 +148,7 @@ namespace Barotrauma
} }
//static because we may need to instantiate the campaign if it hasn't been done yet //static because we may need to instantiate the campaign if it hasn't been done yet
public static void ClientRead(NetBuffer msg) public static void ClientRead(IReadMessage msg)
{ {
byte campaignID = msg.ReadByte(); byte campaignID = msg.ReadByte();
UInt16 updateID = msg.ReadUInt16(); UInt16 updateID = msg.ReadUInt16();
@@ -213,6 +214,8 @@ namespace Barotrauma
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID)) if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID))
{ {
campaign.SuppressStateSending = true;
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex); campaign.Map.SelectMission(selectedMissionIndex);
@@ -236,6 +239,8 @@ namespace Barotrauma
} }
campaign.lastUpdateID = updateID; campaign.lastUpdateID = updateID;
campaign.SuppressStateSending = false;
} }
} }
@@ -218,7 +218,7 @@ namespace Barotrauma.Tutorials
} }
} }
yield return null; yield return null;
} while (!captain_sonar.IsActive); } while (captain_sonar.CurrentMode != Sonar.Mode.Active);
do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f); do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f);
RemoveCompletedObjective(segments[5]); RemoveCompletedObjective(segments[5]);
yield return new WaitForSeconds(4f, false); yield return new WaitForSeconds(4f, false);
@@ -73,6 +73,7 @@ namespace Barotrauma.Tutorials
private Character mechanic; private Character mechanic;
private Sprite mechanic_repairIcon; private Sprite mechanic_repairIcon;
private Color mechanic_repairIconColor; private Color mechanic_repairIconColor;
private Sprite mechanic_weldIcon;
public MechanicTutorial(XElement element) : base(element) public MechanicTutorial(XElement element) : base(element)
{ {
@@ -97,6 +98,7 @@ namespace Barotrauma.Tutorials
var repairOrder = Order.PrefabList.Find(order => order.AITag == "repairsystems"); var repairOrder = Order.PrefabList.Find(order => order.AITag == "repairsystems");
mechanic_repairIcon = repairOrder.SymbolSprite; mechanic_repairIcon = repairOrder.SymbolSprite;
mechanic_repairIconColor = repairOrder.Color; mechanic_repairIconColor = repairOrder.Color;
mechanic_weldIcon = new Sprite("Content/UI/IconAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f));
// Other tutorial items // Other tutorial items
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>(); tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
@@ -309,7 +311,7 @@ namespace Barotrauma.Tutorials
yield return null; yield return null;
} while (!mechanic.HasEquippedItem("divingmask") || !mechanic.HasEquippedItem("weldingtool")); // Wait until equipped } while (!mechanic.HasEquippedItem("divingmask") || !mechanic.HasEquippedItem("weldingtool")); // Wait until equipped
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true); SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true);
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_repairIcon, mechanic_repairIconColor); mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_weldIcon, mechanic_repairIconColor);
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1); mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
RemoveCompletedObjective(segments[2]); RemoveCompletedObjective(segments[2]);
@@ -443,10 +445,10 @@ namespace Barotrauma.Tutorials
{ {
HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher", highlightColor, .5f, .5f, 0f); HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher", highlightColor, .5f, .5f, 0f);
for (int i = 0; i < mechanic.Inventory.slots.Length; i++) /*for (int i = 0; i < mechanic.Inventory.slots.Length; i++)
{ {
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
} }*/
} }
else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium") != null && !mechanic_fabricator.IsActive) else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium") != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium") != null && !mechanic_fabricator.IsActive)
{ {
@@ -521,7 +523,7 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true); SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true);
// Room 7 // Room 7
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_repairIcon, mechanic_repairIconColor); mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_weldIcon, mechanic_repairIconColor);
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2)); do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2));
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2); mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
TriggerTutorialSegment(9, GameMain.Config.KeyBind(InputType.Use)); // Repairing machinery (pump) TriggerTutorialSegment(9, GameMain.Config.KeyBind(InputType.Use)); // Repairing machinery (pump)
@@ -305,10 +305,17 @@ namespace Barotrauma.Tutorials
do do
{ {
float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition); float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition);
if (distance > originalDistance * 1.5f)
{
// Don't let the Hammerhead go too far.
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000));
}
if (distance > originalDistance) if (distance > originalDistance)
{ {
// Don't let the Hammerhead go too far from the periscope. // Ensure that the Hammerhead targets the player
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos); officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
var ai = officer_hammerhead.AIController as EnemyAIController;
ai.sight = 2.0f;
} }
yield return null; yield return null;
} }
@@ -110,21 +110,33 @@ namespace Barotrauma
{ {
infoFrameContent.ClearChildren(); infoFrameContent.ClearChildren();
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), infoFrameContent.RectTransform)) var isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
var missionFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, isTraitor ? 0.95f : 0.45f), infoFrameContent.RectTransform))
{ {
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
if (Mission == null) if (Mission != null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform, Anchor.TopCenter), TextManager.Get("NoMission")); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Name, font: GUI.LargeFont);
return;
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), TextManager.GetWithVariable("MissionReward", "[reward]", Mission.Reward.ToString()));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Description, wrap: true);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform, Anchor.TopCenter), TextManager.Get("NoMission"), font: GUI.LargeFont);
}
if (isTraitor)
{
var traitorFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.45f), infoFrameContent.RectTransform, Anchor.BottomLeft))
{
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), TextManager.Get("Traitors"), font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), GameMain.Client.Character.TraitorCurrentObjective, wrap: true);
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Mission.Name, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), TextManager.GetWithVariable("MissionReward", "[reward]", Mission.Reward.ToString()));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), Mission.Description, wrap: true);
} }
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
@@ -57,10 +57,11 @@ namespace Barotrauma
var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
summaryText, wrap: true); summaryText, wrap: true);
GUIComponent endText = null;
if (!string.IsNullOrWhiteSpace(endMessage)) if (!string.IsNullOrWhiteSpace(endMessage))
{ {
var endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
endMessage, wrap: true); TextManager.GetServerMessage(endMessage), wrap: true);
} }
//don't show the mission info if the mission was not completed and there's no localized "mission failed" text available //don't show the mission info if the mission was not completed and there's no localized "mission failed" text available
@@ -70,7 +71,9 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))
{ {
//spacing //spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), infoTextBox.Content.RectTransform), style: null); var spacingTransform = new RectTransform(new Vector2(1.0f, 0.1f), infoTextBox.Content.RectTransform);
new GUIFrame(spacingTransform, style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("Mission"), GameMain.GameSession.Mission.Name), TextManager.AddPunctuation(':', TextManager.Get("Mission"), GameMain.GameSession.Mission.Name),
@@ -86,12 +89,7 @@ namespace Barotrauma
} }
} }
} }
foreach (GUIComponent child in infoTextBox.Content.Children)
{
child.CanBeFocused = false;
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("RoundSummaryCrewStatus"), font: GUI.LargeFont); TextManager.Get("RoundSummaryCrewStatus"), font: GUI.LargeFont);
@@ -166,6 +164,16 @@ namespace Barotrauma
UserData = "buttonarea" UserData = "buttonarea"
}; };
paddedFrame.Recalculate();
foreach (GUIComponent child in infoTextBox.Content.Children)
{
child.CanBeFocused = false;
if (child is GUITextBlock textBlock)
{
textBlock.CalculateHeightFromText();
}
}
return frame; return frame;
} }
} }
@@ -73,11 +73,23 @@ namespace Barotrauma
public void CreateSettingsFrame(Tab selectedTab = Tab.Graphics) public void CreateSettingsFrame(Tab selectedTab = Tab.Graphics)
{ {
settingsFrame = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.8f), GUI.Canvas, Anchor.Center)); RectTransform settingsHolder = null;
if (Screen.Selected == GameMain.MainMenuScreen)
{
settingsFrame = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.8f), GUI.Canvas, Anchor.Center));
settingsHolder = settingsFrame.RectTransform;
}
else
{
settingsFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: null, color: Color.Black * 0.5f);
var settingsFrameContent = new GUIFrame(new RectTransform(new Vector2(0.8f, 0.8f), settingsFrame.RectTransform, Anchor.Center));
settingsHolder = settingsFrameContent.RectTransform;
}
Vector2 tickBoxScale = Vector2.One * 0.05f; Vector2 tickBoxScale = Vector2.One * 0.05f;
var settingsFramePadding = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), settingsFrame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) }) { RelativeSpacing = 0.01f, IsHorizontal = true }; var settingsFramePadding = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), settingsHolder, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) }) { RelativeSpacing = 0.01f, IsHorizontal = true };
/// General tab -------------------------------------------------------------- /// General tab --------------------------------------------------------------
@@ -104,6 +116,10 @@ namespace Barotrauma
OnSelected = SelectContentPackage, OnSelected = SelectContentPackage,
Selected = SelectedContentPackages.Contains(contentPackage) Selected = SelectedContentPackages.Contains(contentPackage)
}; };
if (contentPackage.CorePackage)
{
tickBox.TextColor = Color.White;
}
if (!contentPackage.IsCompatible()) if (!contentPackage.IsCompatible())
{ {
tickBox.TextColor = Color.Red; tickBox.TextColor = Color.Red;
@@ -251,11 +267,11 @@ namespace Barotrauma
Selected = VSyncEnabled Selected = VSyncEnabled
}; };
//TODO: remove hardcoded texts after the texts have been added to localization
GUITickBox pauseOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, leftColumn.RectTransform, scaleBasis: ScaleBasis.BothHeight), GUITickBox pauseOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, leftColumn.RectTransform, scaleBasis: ScaleBasis.BothHeight),
TextManager.Get("PauseOnFocusLost", returnNull: true) ?? "Pause on focus lost"); TextManager.Get("PauseOnFocusLost"));
pauseOnFocusLostBox.Selected = PauseOnFocusLost; pauseOnFocusLostBox.Selected = PauseOnFocusLost;
pauseOnFocusLostBox.ToolTip = TextManager.Get("PauseOnFocusLostToolTip", returnNull: true) ?? "Pauses the game when its window is not in focus. Note that the game won't be paused when a multiplayer session is active."; pauseOnFocusLostBox.ToolTip = TextManager.Get("PauseOnFocusLostToolTip");
pauseOnFocusLostBox.OnSelected = (tickBox) => pauseOnFocusLostBox.OnSelected = (tickBox) =>
{ {
PauseOnFocusLost = tickBox.Selected; PauseOnFocusLost = tickBox.Selected;
@@ -377,12 +393,12 @@ namespace Barotrauma
/// Audio tab ---------------------------------------------------------------- /// Audio tab ----------------------------------------------------------------
var audioSliders = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopCenter) var audioSliders = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.3f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopCenter)
{ RelativeOffset = new Vector2(0.0f, 0.02f) }) { RelativeOffset = new Vector2(0.0f, 0.02f) })
{ RelativeSpacing = 0.01f }; { RelativeSpacing = 0.01f };
GUITextBlock soundVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TextManager.Get("SoundVolume")); GUITextBlock soundVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), audioSliders.RectTransform), TextManager.Get("SoundVolume"));
GUIScrollBar soundScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), GUIScrollBar soundScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.15f), audioSliders.RectTransform),
barSize: 0.05f) barSize: 0.05f)
{ {
UserData = soundVolumeText, UserData = soundVolumeText,
@@ -397,8 +413,8 @@ namespace Barotrauma
}; };
soundScrollBar.OnMoved(soundScrollBar, soundScrollBar.BarScroll); soundScrollBar.OnMoved(soundScrollBar, soundScrollBar.BarScroll);
GUITextBlock musicVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TextManager.Get("MusicVolume")); GUITextBlock musicVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), audioSliders.RectTransform), TextManager.Get("MusicVolume"));
GUIScrollBar musicScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), GUIScrollBar musicScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.15f), audioSliders.RectTransform),
barSize: 0.05f) barSize: 0.05f)
{ {
UserData = musicVolumeText, UserData = musicVolumeText,
@@ -413,8 +429,8 @@ namespace Barotrauma
}; };
musicScrollBar.OnMoved(musicScrollBar, musicScrollBar.BarScroll); musicScrollBar.OnMoved(musicScrollBar, musicScrollBar.BarScroll);
GUITextBlock voiceChatVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TextManager.Get("VoiceChatVolume")); GUITextBlock voiceChatVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), audioSliders.RectTransform), TextManager.Get("VoiceChatVolume"));
GUIScrollBar voiceChatScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), GUIScrollBar voiceChatScrollBar = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.15f), audioSliders.RectTransform),
barSize: 0.05f) barSize: 0.05f)
{ {
UserData = voiceChatVolumeText, UserData = voiceChatVolumeText,
@@ -429,7 +445,17 @@ namespace Barotrauma
}; };
voiceChatScrollBar.OnMoved(voiceChatScrollBar, voiceChatScrollBar.BarScroll); voiceChatScrollBar.OnMoved(voiceChatScrollBar, voiceChatScrollBar.BarScroll);
GUITickBox muteOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, audioSliders.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get("MuteOnFocusLost")); var leftTickBoxes = new GUILayoutGroup(new RectTransform(new Vector2(0.28f, 0.06f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopLeft)
{ RelativeOffset = new Vector2(0.02f, 0.32f) })
{ RelativeSpacing = 0.01f };
var centerTickBoxes = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.06f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopLeft)
{ RelativeOffset = new Vector2(0.3f, 0.32f) })
{ RelativeSpacing = 0.01f };
var rightTickBoxes = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.06f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopRight)
{ RelativeOffset = new Vector2(0.02f, 0.32f) })
{ RelativeSpacing = 0.01f };
GUITickBox muteOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale / 0.06f, leftTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get("MuteOnFocusLost"));
muteOnFocusLostBox.Selected = MuteOnFocusLost; muteOnFocusLostBox.Selected = MuteOnFocusLost;
muteOnFocusLostBox.ToolTip = TextManager.Get("MuteOnFocusLostToolTip"); muteOnFocusLostBox.ToolTip = TextManager.Get("MuteOnFocusLostToolTip");
muteOnFocusLostBox.OnSelected = (tickBox) => muteOnFocusLostBox.OnSelected = (tickBox) =>
@@ -439,7 +465,31 @@ namespace Barotrauma
return true; return true;
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TextManager.Get("VoiceChat")); GUITickBox dynamicRangeCompressionTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.06f, centerTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get("DynamicRangeCompression"));
dynamicRangeCompressionTickBox.Selected = DynamicRangeCompressionEnabled;
dynamicRangeCompressionTickBox.ToolTip = TextManager.Get("DynamicRangeCompressionToolTip");
dynamicRangeCompressionTickBox.OnSelected = (tickBox) =>
{
DynamicRangeCompressionEnabled = tickBox.Selected;
UnsavedSettings = true;
return true;
};
GUITickBox voipAttenuationTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.06f, rightTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get("VoipAttenuation"));
voipAttenuationTickBox.Selected = VoipAttenuationEnabled;
voipAttenuationTickBox.ToolTip = TextManager.Get("VoipAttenuationToolTip");
voipAttenuationTickBox.OnSelected = (tickBox) =>
{
VoipAttenuationEnabled = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var voipSettings = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.4f), tabs[(int)Tab.Audio].RectTransform, Anchor.TopCenter)
{ RelativeOffset = new Vector2(0.0f, 0.38f) })
{ RelativeSpacing = 0.01f };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), voipSettings.RectTransform), TextManager.Get("VoiceChat"));
IList<string> deviceNames = Alc.GetStringList((IntPtr)null, Alc.CaptureDeviceSpecifier); IList<string> deviceNames = Alc.GetStringList((IntPtr)null, Alc.CaptureDeviceSpecifier);
foreach (string name in deviceNames) foreach (string name in deviceNames)
@@ -447,7 +497,7 @@ namespace Barotrauma
DebugConsole.NewMessage(name + " " + name.Length.ToString(), Color.Lime); DebugConsole.NewMessage(name + " " + name.Length.ToString(), Color.Lime);
} }
GUITickBox directionalVoiceChat = new GUITickBox(new RectTransform(tickBoxScale, audioSliders.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get("DirectionalVoiceChat")); GUITickBox directionalVoiceChat = new GUITickBox(new RectTransform(tickBoxScale / 0.4f, voipSettings.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get("DirectionalVoiceChat"));
directionalVoiceChat.Selected = UseDirectionalVoiceChat; directionalVoiceChat.Selected = UseDirectionalVoiceChat;
directionalVoiceChat.ToolTip = TextManager.Get("DirectionalVoiceChatToolTip"); directionalVoiceChat.ToolTip = TextManager.Get("DirectionalVoiceChatToolTip");
directionalVoiceChat.OnSelected = (tickBox) => directionalVoiceChat.OnSelected = (tickBox) =>
@@ -466,7 +516,7 @@ namespace Barotrauma
VoiceSetting = VoiceMode.Disabled; VoiceSetting = VoiceMode.Disabled;
} }
#if (!OSX) #if (!OSX)
var deviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TrimAudioDeviceName(VoiceCaptureDevice), deviceNames.Count); var deviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), voipSettings.RectTransform), TrimAudioDeviceName(VoiceCaptureDevice), deviceNames.Count);
if (deviceNames?.Count > 0) if (deviceNames?.Count > 0)
{ {
foreach (string name in deviceNames) foreach (string name in deviceNames)
@@ -490,7 +540,7 @@ namespace Barotrauma
} }
#else #else
var defaultDeviceGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), audioSliders.RectTransform), true, Anchor.CenterLeft); var defaultDeviceGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), voipSettings.RectTransform), true, Anchor.CenterLeft);
var currentDeviceTextBlock = new GUITextBlock(new RectTransform(new Vector2(.7f, 0.75f), null), var currentDeviceTextBlock = new GUITextBlock(new RectTransform(new Vector2(.7f, 0.75f), null),
TextManager.AddPunctuation(':', TextManager.Get("CurrentDevice"), TrimAudioDeviceName(VoiceCaptureDevice))) TextManager.AddPunctuation(':', TextManager.Get("CurrentDevice"), TrimAudioDeviceName(VoiceCaptureDevice)))
{ {
@@ -526,13 +576,12 @@ namespace Barotrauma
currentDeviceTextBlock.RectTransform.Parent = defaultDeviceGroup.RectTransform; currentDeviceTextBlock.RectTransform.Parent = defaultDeviceGroup.RectTransform;
#endif #endif
//var radioButtonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.12f), audioSliders.RectTransform));
GUIRadioButtonGroup voiceMode = new GUIRadioButtonGroup(); GUIRadioButtonGroup voiceMode = new GUIRadioButtonGroup();
for (int i = 0; i < 3; i++) for (int i = 0; i < 3; i++)
{ {
string langStr = "VoiceMode." + ((VoiceMode)i).ToString(); string langStr = "VoiceMode." + ((VoiceMode)i).ToString();
var tick = new GUITickBox(new RectTransform(tickBoxScale, audioSliders.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get(langStr)) var tick = new GUITickBox(new RectTransform(tickBoxScale / 0.4f, voipSettings.RectTransform, scaleBasis: ScaleBasis.BothHeight), TextManager.Get(langStr))
{ {
ToolTip = TextManager.Get(langStr + "ToolTip") ToolTip = TextManager.Get(langStr + "ToolTip")
}; };
@@ -540,8 +589,8 @@ namespace Barotrauma
voiceMode.AddRadioButton((VoiceMode)i, tick); voiceMode.AddRadioButton((VoiceMode)i, tick);
} }
var micVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), TextManager.Get("MicrophoneVolume")); var micVolumeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), voipSettings.RectTransform), TextManager.Get("MicrophoneVolume"));
var micVolumeSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.05f), audioSliders.RectTransform), var micVolumeSlider = new GUIScrollBar(new RectTransform(new Vector2(1.0f, 0.15f), voipSettings.RectTransform),
barSize: 0.05f) barSize: 0.05f)
{ {
UserData = micVolumeText, UserData = micVolumeText,
@@ -559,7 +608,7 @@ namespace Barotrauma
micVolumeSlider.OnMoved(micVolumeSlider, micVolumeSlider.BarScroll); micVolumeSlider.OnMoved(micVolumeSlider, micVolumeSlider.BarScroll);
var extraVoiceSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), audioSliders.RectTransform, Anchor.BottomCenter), style: null); var extraVoiceSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.6f), voipSettings.RectTransform, Anchor.BottomCenter), style: null);
var voiceInputContainer = new GUILayoutGroup(new RectTransform(Vector2.One, extraVoiceSettingsContainer.RectTransform, Anchor.BottomCenter)); var voiceInputContainer = new GUILayoutGroup(new RectTransform(Vector2.One, extraVoiceSettingsContainer.RectTransform, Anchor.BottomCenter));
new GUITextBlock(new RectTransform(new Vector2(0.6f, 0.25f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice")); new GUITextBlock(new RectTransform(new Vector2(0.6f, 0.25f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"));
@@ -107,6 +107,17 @@ namespace Barotrauma
SetSlotPositions(layout); SetSlotPositions(layout);
} }
protected override ItemInventory GetActiveEquippedSubInventory(int slotIndex)
{
var item = Items[slotIndex];
if (item == null) return null;
var container = item.GetComponent<ItemContainer>();
if (container == null || !container.KeepOpenWhenEquipped || !character.HasEquippedItem(container.Item)) return null;
return container.Inventory;
}
protected override void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true) protected override void PutItem(Item item, int i, Character user, bool removeItem = true, bool createNetworkEvent = true)
{ {
base.PutItem(item, i, user, removeItem, createNetworkEvent); base.PutItem(item, i, user, removeItem, createNetworkEvent);
@@ -456,7 +467,7 @@ namespace Barotrauma
} }
} }
} }
List<SlotReference> hideSubInventories = new List<SlotReference>(); List<SlotReference> hideSubInventories = new List<SlotReference>();
foreach (var highlightedSubInventorySlot in highlightedSubInventorySlots) foreach (var highlightedSubInventorySlot in highlightedSubInventorySlots)
{ {
@@ -465,6 +476,8 @@ namespace Barotrauma
UpdateSubInventory(deltaTime, highlightedSubInventorySlot.SlotIndex, cam); UpdateSubInventory(deltaTime, highlightedSubInventorySlot.SlotIndex, cam);
} }
if (!highlightedSubInventorySlot.Inventory.IsInventoryHoverAvailable(character, null)) continue;
Rectangle hoverArea = GetSubInventoryHoverArea(highlightedSubInventorySlot); Rectangle hoverArea = GetSubInventoryHoverArea(highlightedSubInventorySlot);
if (highlightedSubInventorySlot.Inventory?.slots == null || (!hoverArea.Contains(PlayerInput.MousePosition))) if (highlightedSubInventorySlot.Inventory?.slots == null || (!hoverArea.Contains(PlayerInput.MousePosition)))
{ {
@@ -476,35 +489,16 @@ namespace Barotrauma
} }
} }
if (doubleClickedItem != null)
{
QuickUseItem(doubleClickedItem, true, true, true);
}
//activate the subinventory of the currently selected slot //activate the subinventory of the currently selected slot
if (selectedSlot?.ParentInventory == this) if (selectedSlot?.ParentInventory == this)
{ {
var subInventory = GetSubInventory(selectedSlot.SlotIndex); var subInventory = GetSubInventory(selectedSlot.SlotIndex);
if (subInventory != null) if (subInventory != null && subInventory.IsInventoryHoverAvailable(character, null))
{ {
selectedSlot.Inventory = subInventory; selectedSlot.Inventory = subInventory;
if (!highlightedSubInventorySlots.Any(s => s.Inventory == subInventory)) if (!highlightedSubInventorySlots.Any(s => s.Inventory == subInventory))
{ {
var slot = selectedSlot; ShowSubInventory(selectedSlot, deltaTime, cam, hideSubInventories, false);
highlightedSubInventorySlots.Add(selectedSlot);
UpdateSubInventory(deltaTime, selectedSlot.SlotIndex, cam);
//hide previously opened subinventories if this one overlaps with them
Rectangle hoverArea = GetSubInventoryHoverArea(slot);
foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
{
if (highlightedSubInventorySlot == slot) continue;
if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
{
hideSubInventories.Add(highlightedSubInventorySlot);
highlightedSubInventorySlot.Inventory.HideTimer = 0.0f;
}
}
} }
} }
} }
@@ -513,66 +507,49 @@ namespace Barotrauma
{ {
if (subInventorySlot.Inventory == null) continue; if (subInventorySlot.Inventory == null) continue;
subInventorySlot.Inventory.HideTimer -= deltaTime; subInventorySlot.Inventory.HideTimer -= deltaTime;
if (subInventorySlot.Inventory.HideTimer <= 0.0f) if (subInventorySlot.Inventory.HideTimer < 0.25f)
{ {
highlightedSubInventorySlots.Remove(subInventorySlot); highlightedSubInventorySlots.Remove(subInventorySlot);
} }
} }
for (int i = 0; i < capacity; i++) if (character.SelectedCharacter == null) // Permanently open subinventories only available when the default UI layout is in use -> not when grabbing characters
{ {
if (Items[i] != null && Items[i].AllowedSlots.Any(a => a != InvSlotType.Any)) for (int i = 0; i < capacity; i++)
{ {
slots[i].EquipButtonState = slots[i].EquipButtonRect.Contains(PlayerInput.MousePosition) ? var item = Items[i];
GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None; if (item != null)
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld())
{ {
slots[i].EquipButtonState = GUIComponent.ComponentState.None; if (HideSlot(i)) continue;
} if (character.HasEquippedItem(item)) // Keep a subinventory display open permanently when the container is equipped
if (slots[i].EquipButtonState != GUIComponent.ComponentState.Hover)
{
slots[i].QuickUseTimer = Math.Max(0.0f, slots[i].QuickUseTimer - deltaTime * 5.0f);
continue;
}
var quickUseAction = GetQuickUseAction(Items[i], allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
slots[i].QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ?
"" : TextManager.Get("QuickUseAction." + quickUseAction.ToString());
//equipped item that can't be put in the inventory, use delayed dropping
if (quickUseAction == QuickUseAction.Drop)
{
slots[i].QuickUseButtonToolTip =
TextManager.Get("QuickUseAction.HoldToUnequip", returnNull: true) ??
(GameMain.Config.Language == "English" ? "Hold to unequip" : TextManager.Get("QuickUseAction.Unequip"));
if (PlayerInput.LeftButtonHeld())
{ {
slots[i].QuickUseTimer = Math.Max(0.1f, slots[i].QuickUseTimer + deltaTime); var itemContainer = item.GetComponent<ItemContainer>();
if (slots[i].QuickUseTimer >= 1.0f) if (itemContainer != null && itemContainer.KeepOpenWhenEquipped && !highlightedSubInventorySlots.Any(s => s.Inventory == itemContainer.Inventory))
{ {
Items[i].Drop(Character.Controlled); ShowSubInventory(new SlotReference(this, slots[i], i, false, itemContainer.Inventory), deltaTime, cam, hideSubInventories, true);
GUI.PlayUISound(GUISoundType.DropItem);
} }
} }
else
{
slots[i].QuickUseTimer = Math.Max(0.0f, slots[i].QuickUseTimer - deltaTime * 5.0f);
}
} }
else
{
if (PlayerInput.LeftButtonDown()) slots[i].EquipButtonState = GUIComponent.ComponentState.Pressed;
if (PlayerInput.LeftButtonClicked())
{
QuickUseItem(Items[i], allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
}
}
} }
} }
if (doubleClickedItem != null)
{
QuickUseItem(doubleClickedItem, true, true, true);
}
for (int i = 0; i < capacity; i++)
{
var item = Items[i];
if (item != null)
{
var slot = slots[i];
if (item.AllowedSlots.Any(a => a != InvSlotType.Any))
{
HandleButtonEquipStates(item, slot, deltaTime);
}
}
}
//cancel dragging if too far away from the container of the dragged item //cancel dragging if too far away from the container of the dragged item
if (draggingItem != null) if (draggingItem != null)
@@ -603,6 +580,89 @@ namespace Barotrauma
doubleClickedItem = null; doubleClickedItem = null;
} }
private void HandleButtonEquipStates(Item item, InventorySlot slot, float deltaTime)
{
slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ?
GUIComponent.ComponentState.Hover : GUIComponent.ComponentState.None;
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld())
{
slot.EquipButtonState = GUIComponent.ComponentState.None;
}
if (slot.EquipButtonState != GUIComponent.ComponentState.Hover)
{
slot.QuickUseTimer = Math.Max(0.0f, slot.QuickUseTimer - deltaTime * 5.0f);
return;
}
var quickUseAction = GetQuickUseAction(item, allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
slot.QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ?
"" : TextManager.Get("QuickUseAction." + quickUseAction.ToString());
//equipped item that can't be put in the inventory, use delayed dropping
if (quickUseAction == QuickUseAction.Drop)
{
slot.QuickUseButtonToolTip =
TextManager.Get("QuickUseAction.HoldToUnequip", returnNull: true) ??
(GameMain.Config.Language == "English" ? "Hold to unequip" : TextManager.Get("QuickUseAction.Unequip"));
if (PlayerInput.LeftButtonHeld())
{
slot.QuickUseTimer = Math.Max(0.1f, slot.QuickUseTimer + deltaTime);
if (slot.QuickUseTimer >= 1.0f)
{
item.Drop(Character.Controlled);
GUI.PlayUISound(GUISoundType.DropItem);
}
}
else
{
slot.QuickUseTimer = Math.Max(0.0f, slot.QuickUseTimer - deltaTime * 5.0f);
}
}
else
{
if (PlayerInput.LeftButtonDown()) slot.EquipButtonState = GUIComponent.ComponentState.Pressed;
if (PlayerInput.LeftButtonClicked())
{
QuickUseItem(item, allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
}
}
}
private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory)
{
Rectangle hoverArea = GetSubInventoryHoverArea(slotRef);
if (isEquippedSubInventory)
{
foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
{
if (highlightedSubInventorySlot == slotRef) continue;
if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
{
return; // If an equipped one intersects with a currently active hover one, do not open
}
}
}
slotRef.Inventory.OpenState = isEquippedSubInventory ? 1f : 0f; // Reset animation when initially equipped
highlightedSubInventorySlots.Add(slotRef);
slotRef.Inventory.HideTimer = 1f;
UpdateSubInventory(deltaTime, slotRef.SlotIndex, cam);
//hide previously opened subinventories if this one overlaps with them
foreach (SlotReference highlightedSubInventorySlot in highlightedSubInventorySlots)
{
if (highlightedSubInventorySlot == slotRef) continue;
if (hoverArea.Intersects(GetSubInventoryHoverArea(highlightedSubInventorySlot)))
{
hideSubInventories.Add(highlightedSubInventorySlot);
highlightedSubInventorySlot.Inventory.HideTimer = 0.0f;
}
}
}
private void AssignQuickUseNumKeys() private void AssignQuickUseNumKeys()
{ {
@@ -633,14 +693,34 @@ namespace Barotrauma
if (item.ParentInventory != this) if (item.ParentInventory != this)
{ {
//in another inventory -> attempt to place in the character's inventory //in another inventory -> attempt to place in the character's inventory
if (item.ParentInventory.Locked) if (item.ParentInventory.Locked || item.ParentInventory == null)
{ {
return QuickUseAction.None; return QuickUseAction.None;
} }
else if (allowInventorySwap) else if (allowInventorySwap)
{ {
return item.ParentInventory is CharacterInventory ? if (item.Container == null || character.Inventory.FindIndex(item.Container) == -1) // Not a subinventory in the character's inventory
QuickUseAction.TakeFromCharacter : QuickUseAction.TakeFromContainer; {
return item.ParentInventory is CharacterInventory ?
QuickUseAction.TakeFromCharacter : QuickUseAction.TakeFromContainer;
}
else
{
var selectedContainer = character.SelectedConstruction?.GetComponent<ItemContainer>();
if (selectedContainer != null &&
selectedContainer.Inventory != null &&
!selectedContainer.Inventory.Locked &&
allowInventorySwap)
{
// Move the item from the subinventory to the selected container
return QuickUseAction.PutToContainer;
}
else
{
// Take from the subinventory and place it in the character's main inventory if no target container is selected
return QuickUseAction.TakeFromContainer;
}
}
} }
} }
else else
@@ -756,7 +836,23 @@ namespace Barotrauma
} }
break; break;
case QuickUseAction.TakeFromContainer: case QuickUseAction.TakeFromContainer:
success = TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true); // Check open subinventories and put the item in it if equipped
ItemInventory activeSubInventory = null;
for (int i = 0; i < capacity; i++)
{
activeSubInventory = GetActiveEquippedSubInventory(i);
if (activeSubInventory != null)
{
success = activeSubInventory.TryPutItem(item, Character.Controlled, item.AllowedSlots, true);
break;
}
}
// No subinventory found or placing unsuccessful -> attempt to put in the character's inventory
if (!success)
{
success = TryPutItemWithAutoEquipCheck(item, Character.Controlled, item.AllowedSlots, true);
}
break; break;
} }
@@ -134,7 +134,7 @@ namespace Barotrauma.Items.Components
if (item.Submarine != null) pos += item.Submarine.DrawPosition; if (item.Submarine != null) pos += item.Submarine.DrawPosition;
pos.Y = -pos.Y; pos.Y = -pos.Y;
if (brokenSprite == null || item.Health > 0.0f) if (brokenSprite == null || !IsBroken)
{ {
spriteBatch.Draw(doorSprite.Texture, pos, spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle((int) (doorSprite.SourceRect.X + doorSprite.size.X * openState), new Rectangle((int) (doorSprite.SourceRect.X + doorSprite.size.X * openState),
@@ -160,7 +160,7 @@ namespace Barotrauma.Items.Components
if (item.Submarine != null) pos += item.Submarine.DrawPosition; if (item.Submarine != null) pos += item.Submarine.DrawPosition;
pos.Y = -pos.Y; pos.Y = -pos.Y;
if (brokenSprite == null || item.Health > 0.0f) if (brokenSprite == null || !IsBroken)
{ {
spriteBatch.Draw(doorSprite.Texture, pos, spriteBatch.Draw(doorSprite.Texture, pos,
new Rectangle(doorSprite.SourceRect.X, new Rectangle(doorSprite.SourceRect.X,
@@ -217,7 +217,7 @@ namespace Barotrauma.Items.Components
} }
public override void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime) public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
base.ClientRead(type, msg, sendingTime); base.ClientRead(type, msg, sendingTime);
@@ -1,6 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -188,7 +187,9 @@ namespace Barotrauma.Items.Components
if (loopingSound != null) if (loopingSound != null)
{ {
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(position.X, position.Y, 0.0f)) > loopingSound.Range * loopingSound.Range) float targetGain = 0.0f;
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(position.X, position.Y, 0.0f)) > loopingSound.Range * loopingSound.Range ||
(targetGain = GetSoundVolume(loopingSound)) <= 0.0001f)
{ {
if (loopingSoundChannel != null) if (loopingSoundChannel != null)
{ {
@@ -220,7 +221,6 @@ namespace Barotrauma.Items.Components
lastMuffleCheckTime = (float)Timing.TotalTime; lastMuffleCheckTime = (float)Timing.TotalTime;
} }
loopingSoundChannel.Muffled = shouldMuffleLooping; loopingSoundChannel.Muffled = shouldMuffleLooping;
float targetGain = GetSoundVolume(loopingSound);
float gainDiff = targetGain - loopingSoundChannel.Gain; float gainDiff = targetGain - loopingSoundChannel.Gain;
loopingSoundChannel.Gain += Math.Abs(gainDiff) < 0.1f ? gainDiff : Math.Sign(gainDiff) * 0.1f; 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);
@@ -258,7 +258,6 @@ namespace Barotrauma.Items.Components
itemSound = matchingSounds[index]; itemSound = matchingSounds[index];
PlaySound(matchingSounds[index], position, user); PlaySound(matchingSounds[index], position, user);
} }
} }
@@ -278,6 +277,8 @@ namespace Barotrauma.Items.Components
} }
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{ {
float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; }
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),
0.01f, 0.01f,
@@ -291,7 +292,7 @@ namespace Barotrauma.Items.Components
else else
{ {
float volume = GetSoundVolume(itemSound); float volume = GetSoundVolume(itemSound);
if (volume <= 0.0f) { return; } if (volume <= 0.0001f) { return; }
SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull); SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull);
} }
} }
@@ -465,14 +466,14 @@ namespace Barotrauma.Items.Components
} }
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
protected void StartDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync = false) protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{ {
if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine); if (delayedCorrectionCoroutine != null) CoroutineManager.StopCoroutines(delayedCorrectionCoroutine);
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync)); delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync));
} }
private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, NetBuffer buffer, float sendingTime, bool waitForMidRoundSync) private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
{ {
while (GameMain.Client != null && while (GameMain.Client != null &&
(correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing))) (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
@@ -73,6 +73,11 @@ namespace Barotrauma.Items.Components
set; set;
} }
[Serialize(false, false)]
public bool KeepOpenWhenEquipped { get; set; }
[Serialize(false, false)]
public bool MovableFrame { get; set; }
public Vector2 DrawSize public Vector2 DrawSize
{ {
//use the extents of the item as the draw size //use the extents of the item as the draw size
@@ -1,11 +1,10 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
partial class LevelResource : ItemComponent, IServerSerializable partial class LevelResource : ItemComponent, IServerSerializable
{ {
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
deattachTimer = msg.ReadSingle(); deattachTimer = msg.ReadSingle();
if (deattachTimer >= DeattachDuration) if (deattachTimer >= DeattachDuration)
@@ -1,6 +1,5 @@
using Barotrauma.Lights; using Barotrauma.Lights;
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -36,7 +35,7 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
IsOn = msg.ReadBoolean(); IsOn = msg.ReadBoolean();
} }
@@ -38,6 +38,8 @@ namespace Barotrauma.Items.Components
private void ToggleCrewArea(bool value, bool storeOriginalState) private void ToggleCrewArea(bool value, bool storeOriginalState)
{ {
var crewManager = GameMain.GameSession.CrewManager; var crewManager = GameMain.GameSession.CrewManager;
if (crewManager == null) { return; }
if (storeOriginalState) if (storeOriginalState)
{ {
crewAreaOriginalState = crewManager.ToggleCrewAreaOpen; crewAreaOriginalState = crewManager.ToggleCrewAreaOpen;
@@ -48,6 +50,8 @@ namespace Barotrauma.Items.Components
private void ToggleChatBox(bool value, bool storeOriginalState) private void ToggleChatBox(bool value, bool storeOriginalState)
{ {
var crewManager = GameMain.GameSession.CrewManager; var crewManager = GameMain.GameSession.CrewManager;
if (crewManager == null) { return; }
if (crewManager.IsSinglePlayer) if (crewManager.IsSinglePlayer)
{ {
if (crewManager.ChatBox != null) if (crewManager.ChatBox != null)
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
@@ -94,12 +93,12 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.Write(pendingState); msg.Write(pendingState);
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
SetActive(msg.ReadBoolean()); SetActive(msg.ReadBoolean());
progressTimer = msg.ReadSingle(); progressTimer = msg.ReadSingle();
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -134,13 +133,13 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
//targetForce can only be adjusted at 10% intervals -> no need for more accuracy than this //targetForce can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(targetForce / 10.0f)); msg.WriteRangedIntegerDeprecated(-10, 10, (int)(targetForce / 10.0f));
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
@@ -1,6 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -460,13 +459,13 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem); int itemIndex = pendingFabricatedItem == null ? -1 : fabricationRecipes.IndexOf(pendingFabricatedItem);
msg.WriteRangedInteger(-1, fabricationRecipes.Count - 1, itemIndex); msg.WriteRangedIntegerDeprecated(-1, fabricationRecipes.Count - 1, itemIndex);
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1); int itemIndex = msg.ReadRangedInteger(-1, fabricationRecipes.Count - 1);
UInt16 userID = msg.ReadUInt16(); UInt16 userID = msg.ReadUInt16();
@@ -487,4 +486,4 @@ namespace Barotrauma.Items.Components
} }
} }
} }
} }
@@ -17,6 +17,7 @@ namespace Barotrauma.Items.Components
private GUIScrollBar isActiveSlider; private GUIScrollBar isActiveSlider;
private GUIScrollBar pumpSpeedSlider; private GUIScrollBar pumpSpeedSlider;
private GUITickBox powerIndicator; private GUITickBox powerIndicator;
private GUITickBox autoControlIndicator;
private List<Pair<Vector2, ParticleEmitter>> pumpOutEmitters = new List<Pair<Vector2, ParticleEmitter>>(); private List<Pair<Vector2, ParticleEmitter>> pumpOutEmitters = new List<Pair<Vector2, ParticleEmitter>>();
private List<Pair<Vector2, ParticleEmitter>> pumpInEmitters = new List<Pair<Vector2, ParticleEmitter>>(); private List<Pair<Vector2, ParticleEmitter>> pumpInEmitters = new List<Pair<Vector2, ParticleEmitter>>();
@@ -71,12 +72,20 @@ namespace Barotrauma.Items.Components
return true; return true;
}; };
var rightArea = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1.0f), paddedFrame.RectTransform, Anchor.CenterRight)) { RelativeSpacing = 0.1f }; var rightArea = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.95f), paddedFrame.RectTransform, Anchor.CenterRight))
{
RelativeSpacing = 0.1f,
Stretch = true
};
powerIndicator = new GUITickBox(new RectTransform(new Point((int)(30 * GUI.Scale)), rightArea.RectTransform), TextManager.Get("PumpPowered"), style: "IndicatorLightGreen") powerIndicator = new GUITickBox(new RectTransform(new Point((int)(30 * GUI.Scale)), rightArea.RectTransform), TextManager.Get("PumpPowered"), style: "IndicatorLightGreen")
{ {
CanBeFocused = false CanBeFocused = false
}; };
autoControlIndicator = new GUITickBox(new RectTransform(new Point((int)(30 * GUI.Scale)), rightArea.RectTransform), TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), style: "IndicatorLightGreen")
{
CanBeFocused = false
};
var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) }, var pumpSpeedText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), rightArea.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.0f) },
"", textAlignment: Alignment.BottomLeft, wrap: true); "", textAlignment: Alignment.BottomLeft, wrap: true);
@@ -86,12 +95,12 @@ namespace Barotrauma.Items.Components
var sliderArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), rightArea.RectTransform, Anchor.CenterLeft), isHorizontal: true) var sliderArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), rightArea.RectTransform, Anchor.CenterLeft), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.05f RelativeSpacing = 0.01f
}; };
var outLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), sliderArea.RectTransform), var outLabel = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), sliderArea.RectTransform),
TextManager.Get("PumpOut"), textAlignment: Alignment.Center, wrap: true, font: GUI.SmallFont); TextManager.Get("PumpOut"), textAlignment: Alignment.Center, wrap: false, font: GUI.SmallFont);
pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.8f, 1.0f), sliderArea.RectTransform), barSize: 0.25f, style: "GUISlider") pumpSpeedSlider = new GUIScrollBar(new RectTransform(new Vector2(0.5f, 1.0f), sliderArea.RectTransform), barSize: 0.25f, style: "GUISlider")
{ {
Step = 0.05f, Step = 0.05f,
OnMoved = (GUIScrollBar scrollBar, float barScroll) => OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
@@ -109,9 +118,11 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
var inLabel = new GUITextBlock(new RectTransform(new Vector2(0.3f, 1.0f), sliderArea.RectTransform), var inLabel = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1.0f), sliderArea.RectTransform),
TextManager.Get("PumpIn"), textAlignment: Alignment.Center, wrap: true, font: GUI.SmallFont); TextManager.Get("PumpIn"), textAlignment: Alignment.Center, wrap: false, font: GUI.SmallFont);
rightArea.Recalculate();
sliderArea.Recalculate();
GUITextBlock.AutoScaleAndNormalize(outLabel, inLabel); GUITextBlock.AutoScaleAndNormalize(outLabel, inLabel);
} }
@@ -120,7 +131,6 @@ namespace Barotrauma.Items.Components
if (pumpSpeedSlider != null) if (pumpSpeedSlider != null)
{ {
pumpSpeedSlider.BarScroll = (flowPercentage + 100.0f) / 200.0f; pumpSpeedSlider.BarScroll = (flowPercentage + 100.0f) / 200.0f;
} }
} }
@@ -151,6 +161,8 @@ namespace Barotrauma.Items.Components
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
powerIndicator.Selected = hasPower && IsActive; powerIndicator.Selected = hasPower && IsActive;
autoControlIndicator.Selected = controlLockTimer > 0.0f && IsActive;
pumpSpeedSlider.Enabled = controlLockTimer <= 0.0f && IsActive;
if (!PlayerInput.LeftButtonHeld()) if (!PlayerInput.LeftButtonHeld())
{ {
@@ -164,14 +176,14 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this //flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger(-10, 10, (int)(flowPercentage / 10.0f)); msg.WriteRangedIntegerDeprecated(-10, 10, (int)(flowPercentage / 10.0f));
msg.Write(IsActive); msg.Write(IsActive);
} }
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -612,7 +611,7 @@ namespace Barotrauma.Items.Components
tempRangeIndicator.Remove(); tempRangeIndicator.Remove();
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.Write(autoTemp); msg.Write(autoTemp);
msg.Write(shutDown); msg.Write(shutDown);
@@ -622,7 +621,7 @@ namespace Barotrauma.Items.Components
correctionTimer = CorrectionDelay; correctionTimer = CorrectionDelay;
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
@@ -332,15 +332,18 @@ namespace Barotrauma.Items.Components
float dockingDist = Vector2.Distance(steering.ActiveDockingSource.Item.WorldPosition, steering.DockingTarget.Item.WorldPosition); float dockingDist = Vector2.Distance(steering.ActiveDockingSource.Item.WorldPosition, steering.DockingTarget.Item.WorldPosition);
if (prevDockingDist > steering.DockingAssistThreshold && dockingDist <= steering.DockingAssistThreshold) if (prevDockingDist > steering.DockingAssistThreshold && dockingDist <= steering.DockingAssistThreshold)
{ {
zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, 0.25f)); zoomSlider.BarScroll = 0.25f;
zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, zoomSlider.BarScroll));
} }
else if (prevDockingDist > steering.DockingAssistThreshold * 0.75f && dockingDist <= steering.DockingAssistThreshold * 0.75f) else if (prevDockingDist > steering.DockingAssistThreshold * 0.75f && dockingDist <= steering.DockingAssistThreshold * 0.75f)
{ {
zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, 0.5f)); zoomSlider.BarScroll = 0.5f;
zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, zoomSlider.BarScroll));
} }
else if (prevDockingDist > steering.DockingAssistThreshold * 0.5f && dockingDist <= steering.DockingAssistThreshold * 0.5f) else if (prevDockingDist > steering.DockingAssistThreshold * 0.5f && dockingDist <= steering.DockingAssistThreshold * 0.5f)
{ {
zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, 0.25f)); zoomSlider.BarScroll = 0.25f;
zoom = Math.Max(zoom, MathHelper.Lerp(MinZoom, MaxZoom, zoomSlider.BarScroll));
} }
prevDockingDist = Math.Min(dockingDist, prevDockingDist); prevDockingDist = Math.Min(dockingDist, prevDockingDist);
} }
@@ -1181,7 +1184,7 @@ namespace Barotrauma.Items.Components
2, GUI.SmallFont); 2, GUI.SmallFont);
} }
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.Write(currentMode == Mode.Active); msg.Write(currentMode == Mode.Active);
if (currentMode == Mode.Active) if (currentMode == Mode.Active)
@@ -1195,9 +1198,9 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
long msgStartPos = msg.Position; int msgStartPos = msg.BitPosition;
bool isActive = msg.ReadBoolean(); bool isActive = msg.ReadBoolean();
float zoomT = 1.0f; float zoomT = 1.0f;
@@ -1215,8 +1218,8 @@ namespace Barotrauma.Items.Components
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
int msgLength = (int)(msg.Position - msgStartPos); int msgLength = (int)(msg.BitPosition - msgStartPos);
msg.Position = msgStartPos; msg.BitPosition = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime); StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
return; return;
} }
@@ -301,10 +301,9 @@ namespace Barotrauma.Items.Components
dockingContainer = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GuiFrame.RectTransform, Anchor.BottomLeft) dockingContainer = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GuiFrame.RectTransform, Anchor.BottomLeft)
{ MinSize = new Point(150, 0) }, style: null); { MinSize = new Point(150, 0) }, style: null);
var paddedDockingContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), dockingContainer.RectTransform, Anchor.Center), style: null); var paddedDockingContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), dockingContainer.RectTransform, Anchor.Center), style: null);
//TODO: add new texts for these ("Dock" & "Undock") dockText = TextManager.Get("label.navterminaldock", fallBackTag: "captain.dock");
dockText = TextManager.Get("captain.dock"); undockText = TextManager.Get("label.navterminalundock", fallBackTag: "captain.undock");
undockText = TextManager.Get("captain.undock");
dockingButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), paddedDockingContainer.RectTransform, Anchor.Center), dockText, style: "GUIButtonLarge") dockingButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), paddedDockingContainer.RectTransform, Anchor.Center), dockText, style: "GUIButtonLarge")
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
@@ -786,7 +785,7 @@ namespace Barotrauma.Items.Components
steeringIndicator?.Remove(); steeringIndicator?.Remove();
} }
public void ClientWrite(Lidgren.Network.NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.Write(autoPilot); msg.Write(autoPilot);
msg.Write(dockingNetworkMessagePending); msg.Write(dockingNetworkMessagePending);
@@ -813,9 +812,9 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
long msgStartPos = msg.Position; int msgStartPos = msg.BitPosition;
bool autoPilot = msg.ReadBoolean(); bool autoPilot = msg.ReadBoolean();
Vector2 newSteeringInput = steeringInput; Vector2 newSteeringInput = steeringInput;
@@ -831,8 +830,8 @@ namespace Barotrauma.Items.Components
if (maintainPos) if (maintainPos)
{ {
newPosToMaintain = new Vector2( newPosToMaintain = new Vector2(
msg.ReadFloat(), msg.ReadSingle(),
msg.ReadFloat()); msg.ReadSingle());
} }
else else
{ {
@@ -841,15 +840,15 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
newSteeringInput = new Vector2(msg.ReadFloat(), msg.ReadFloat()); newSteeringInput = new Vector2(msg.ReadSingle(), msg.ReadSingle());
newTargetVelocity = new Vector2(msg.ReadFloat(), msg.ReadFloat()); newTargetVelocity = new Vector2(msg.ReadSingle(), msg.ReadSingle());
newSteeringAdjustSpeed = msg.ReadFloat(); newSteeringAdjustSpeed = msg.ReadSingle();
} }
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
int msgLength = (int)(msg.Position - msgStartPos); int msgLength = (int)(msg.BitPosition - msgStartPos);
msg.Position = msgStartPos; msg.BitPosition = msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime); StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime);
return; return;
} }
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -128,12 +127,12 @@ namespace Barotrauma.Items.Components
} }
public void ClientWrite(NetBuffer msg, object[] extraData) public void ClientWrite(IWriteMessage msg, object[] extraData)
{ {
msg.WriteRangedInteger(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10)); msg.WriteRangedIntegerDeprecated(0, 10, (int)(rechargeSpeed / MaxRechargeSpeed * 10));
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
@@ -117,17 +117,20 @@ namespace Barotrauma.Items.Components
float progressBarState = targetItem.ConditionPercentage / 100.0f; float progressBarState = targetItem.ConditionPercentage / 100.0f;
if (!MathUtils.NearlyEqual(progressBarState, prevProgressBarState)) if (!MathUtils.NearlyEqual(progressBarState, prevProgressBarState))
{ {
Vector2 progressBarPos = targetItem.DrawPosition; var door = targetItem.GetComponent<Door>();
var progressBar = user.UpdateHUDProgressBar( if (door == null || door.Stuck <= 0)
targetItem, {
progressBarPos, Vector2 progressBarPos = targetItem.DrawPosition;
progressBarState, var progressBar = user.UpdateHUDProgressBar(
Color.Red, Color.Green); targetItem,
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); } progressBarPos,
progressBarState,
Color.Red, Color.Green);
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
}
prevProgressBarState = progressBarState;
} }
prevProgressBarState = progressBarState;
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition); Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition; if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach (var emitter in ParticleEmitterHitItem) foreach (var emitter in ParticleEmitterHitItem)
@@ -1,6 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic; using System.Collections.Generic;
@@ -15,6 +14,11 @@ namespace Barotrauma.Items.Components
get { return repairButton; } get { return repairButton; }
} }
private GUIButton repairButton; private GUIButton repairButton;
public GUIButton SabotageButton
{
get { return sabotageButton; }
}
private GUIButton sabotageButton;
private GUIProgressBar progressBar; private GUIProgressBar progressBar;
private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
@@ -22,6 +26,9 @@ namespace Barotrauma.Items.Components
private List<Vector2> particleEmitterConditionRanges = new List<Vector2>(); private List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
private string repairButtonText, repairingText; private string repairButtonText, repairingText;
private string sabotageButtonText, sabotagingText;
private FixActions requestStartFixAction;
[Serialize("", false)] [Serialize("", false)]
public string Description public string Description
@@ -39,7 +46,7 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) public override bool ShouldDrawHUD(Character character)
{ {
if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false; if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) return false;
return (item.Condition < ShowRepairUIThreshold || (currentFixer == character && !item.IsFullCondition)); return item.ConditionPercentage < ShowRepairUIThreshold || character.IsTraitor && item.ConditionPercentage > MinSabotageCondition || (CurrentFixer == character && (!item.IsFullCondition || (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition)));
} }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
@@ -61,24 +68,34 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < requiredSkills.Count; i++) for (int i = 0; i < requiredSkills.Count; i++)
{ {
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int)requiredSkills[i].Level).ToString()), " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) requiredSkills[i].Level).ToString()),
font: GUI.SmallFont) font: GUI.SmallFont)
{ {
UserData = requiredSkills[i] UserData = requiredSkills[i]
}; };
} }
progressBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform), progressBar = new GUIProgressBar(new RectTransform(new Vector2(1.0f, 0.15f), paddedFrame.RectTransform),
color: Color.Green, barSize: 0.0f); color: Color.Green, barSize: 0.0f);
repairButtonText = TextManager.Get("RepairButton"); repairButtonText = TextManager.Get("RepairButton");
repairingText = TextManager.Get("Repairing"); repairingText = TextManager.Get("Repairing");
repairButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.TopCenter), repairButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.TopCenter), repairButtonText)
repairButtonText)
{ {
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
currentFixer = Character.Controlled; requestStartFixAction = FixActions.Repair;
item.CreateClientEvent(this);
return true;
}
};
sabotageButtonText = TextManager.Get("SabotageButton");
sabotagingText = TextManager.Get("Sabotaging");
sabotageButton = new GUIButton(new RectTransform(new Vector2(0.8f, 0.15f), paddedFrame.RectTransform, Anchor.BottomCenter), sabotageButtonText)
{
OnClicked = (btn, obj) =>
{
requestStartFixAction = FixActions.Sabotage;
item.CreateClientEvent(this); item.CreateClientEvent(this);
return true; return true;
} }
@@ -101,6 +118,21 @@ namespace Barotrauma.Items.Components
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (!GameMain.IsMultiplayer)
{
switch (requestStartFixAction)
{
case FixActions.Repair:
case FixActions.Sabotage:
StartRepairing(Character.Controlled, requestStartFixAction);
requestStartFixAction = FixActions.None;
break;
default:
requestStartFixAction = FixActions.None;
break;
}
}
for (int i = 0; i < particleEmitters.Count; i++) for (int i = 0; i < particleEmitters.Count; i++)
{ {
if (item.ConditionPercentage >= particleEmitterConditionRanges[i].X && item.ConditionPercentage <= particleEmitterConditionRanges[i].Y) if (item.ConditionPercentage >= particleEmitterConditionRanges[i].X && item.ConditionPercentage <= particleEmitterConditionRanges[i].Y)
@@ -117,11 +149,17 @@ namespace Barotrauma.Items.Components
progressBar.BarSize = item.Condition / item.MaxCondition; progressBar.BarSize = item.Condition / item.MaxCondition;
progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, Color.Red, Color.Orange, Color.Green); progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, Color.Red, Color.Orange, Color.Green);
repairButton.Enabled = currentFixer == null; repairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && item.ConditionPercentage <= ShowRepairUIThreshold;
repairButton.Text = currentFixer == null ? repairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
repairButtonText : repairButtonText :
repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
sabotageButton.Visible = character.IsTraitor;
sabotageButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Sabotage)) && character.IsTraitor && item.ConditionPercentage > MinSabotageCondition;
sabotageButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Sabotage || !character.IsTraitor) ?
sabotageButtonText :
sabotagingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts"); System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");
foreach (GUIComponent c in GuiFrame.GetChild(0).Children) foreach (GUIComponent c in GuiFrame.GetChild(0).Children)
{ {
@@ -139,14 +177,18 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
deteriorationTimer = msg.ReadSingle(); deteriorationTimer = msg.ReadSingle();
deteriorateAlwaysResetTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean();
CurrentFixer = msg.ReadBoolean() ? Character.Controlled : null;
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
//no need to write anything, just letting the server know we started repairing msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
} }
public void Draw(SpriteBatch spriteBatch, bool editing) public void Draw(SpriteBatch spriteBatch, bool editing)
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -30,7 +29,7 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) public override bool ShouldDrawHUD(Character character)
{ {
return character == Character.Controlled && character == user; return character == Character.Controlled && character == user && character.SelectedConstruction == item;
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
@@ -40,7 +39,7 @@ namespace Barotrauma.Items.Components
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
if (character != Character.Controlled || character != user) return; if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; }
if (HighlightedWire != null) if (HighlightedWire != null)
{ {
@@ -64,13 +63,13 @@ namespace Barotrauma.Items.Components
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (GameMain.Client.MidRoundSyncing) if (GameMain.Client.MidRoundSyncing)
{ {
//delay reading the state until midround syncing is done //delay reading the state until midround syncing is done
//because some of the wires connected to the panel may not exist yet //because some of the wires connected to the panel may not exist yet
long msgStartPos = msg.Position; long msgStartPos = msg.BitPosition;
foreach (Connection connection in Connections) foreach (Connection connection in Connections)
{ {
for (int i = 0; i < Connection.MaxLinked; i++) for (int i = 0; i < Connection.MaxLinked; i++)
@@ -83,8 +82,8 @@ namespace Barotrauma.Items.Components
{ {
msg.ReadUInt16(); msg.ReadUInt16();
} }
int msgLength = (int)(msg.Position - msgStartPos); int msgLength = (int)(msg.BitPosition - msgStartPos);
msg.Position = msgStartPos; msg.BitPosition = (int)msgStartPos;
StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime, waitForMidRoundSync: true); StartDelayedCorrection(type, msg.ExtractBits(msgLength), sendingTime, waitForMidRoundSync: true);
} }
else else
@@ -93,7 +92,7 @@ namespace Barotrauma.Items.Components
} }
} }
private void ApplyRemoteState(NetBuffer msg) private void ApplyRemoteState(IReadMessage msg)
{ {
List<Wire> prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList(); List<Wire> prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList();
List<Wire> newWires = new List<Wire>(); List<Wire> newWires = new List<Wire>();
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -132,7 +131,7 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
//extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything) //extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
for (int i = 0; i < customInterfaceElementList.Count; i++) for (int i = 0; i < customInterfaceElementList.Count; i++)
@@ -148,7 +147,7 @@ namespace Barotrauma.Items.Components
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
for (int i = 0; i < customInterfaceElementList.Count; i++) for (int i = 0; i < customInterfaceElementList.Count; i++)
{ {
@@ -1,7 +1,6 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -140,6 +139,11 @@ namespace Barotrauma.Items.Components
}; };
} }
public override void Move(Vector2 amount)
{
widgets.Clear();
}
partial void LaunchProjSpecific() partial void LaunchProjSpecific()
{ {
recoilTimer = Math.Max(Reload, 0.1f); recoilTimer = Math.Max(Reload, 0.1f);
@@ -496,7 +500,7 @@ namespace Barotrauma.Items.Components
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, zoom); crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, zoom);
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
UInt16 projectileID = msg.ReadUInt16(); UInt16 projectileID = msg.ReadUInt16();
//projectile removed, do nothing //projectile removed, do nothing
@@ -1,7 +1,6 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
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;
@@ -20,22 +19,22 @@ namespace Barotrauma
public bool Disabled; public bool Disabled;
public GUIComponent.ComponentState State; public GUIComponent.ComponentState State;
public Vector2 DrawOffset; public Vector2 DrawOffset;
public Color Color; public Color Color;
public Color HighlightColor; public Color HighlightColor;
public float HighlightScaleUpAmount; public float HighlightScaleUpAmount;
private CoroutineHandle highlightCoroutine; private CoroutineHandle highlightCoroutine;
public float HighlightTimer; public float HighlightTimer;
public Sprite SlotSprite; public Sprite SlotSprite;
public Keys QuickUseKey; public Keys QuickUseKey;
public int SubInventoryDir = -1; public int SubInventoryDir = -1;
public bool IsHighlighted public bool IsHighlighted
{ {
get get
@@ -130,13 +129,15 @@ namespace Barotrauma
protected float prevUIScale = UIScale; protected float prevUIScale = UIScale;
protected float prevHUDScale = GUI.Scale; protected float prevHUDScale = GUI.Scale;
protected Point prevScreenResolution;
protected static Sprite slotSpriteSmall, slotSpriteHorizontal, slotSpriteVertical, slotSpriteRound; protected static Sprite slotSpriteSmall, slotSpriteHorizontal, slotSpriteVertical, slotSpriteRound;
public static Sprite EquipIndicator, EquipIndicatorHighlight; public static Sprite EquipIndicator, EquipIndicatorHighlight;
public static Sprite DropIndicator, DropIndicatorHighlight; public static Sprite DropIndicator, DropIndicatorHighlight;
public static Inventory DraggingInventory;
public Rectangle BackgroundFrame { get; protected set; } public Rectangle BackgroundFrame { get; protected set; }
private ushort[] receivedItemIDs; private ushort[] receivedItemIDs;
private CoroutineHandle syncItemsCoroutine; private CoroutineHandle syncItemsCoroutine;
@@ -144,6 +145,13 @@ namespace Barotrauma
private bool isSubInventory; private bool isSubInventory;
private const float movableFrameRectHeight = 40f;
private Color movableFrameRectColor = new Color(60, 60, 60);
private Rectangle movableFrameRect;
private Point savedPosition, originalPos;
private bool canMove = false;
private bool positionUpdateQueued = false;
public class SlotReference public class SlotReference
{ {
public readonly Inventory ParentInventory; public readonly Inventory ParentInventory;
@@ -198,12 +206,12 @@ namespace Barotrauma
/// If set, the inventory is automatically positioned inside the rect /// If set, the inventory is automatically positioned inside the rect
/// </summary> /// </summary>
public RectTransform RectTransform; public RectTransform RectTransform;
public static SlotReference SelectedSlot public static SlotReference SelectedSlot
{ {
get { return selectedSlot; } get { return selectedSlot; }
} }
public virtual void CreateSlots() public virtual void CreateSlots()
{ {
slots = new InventorySlot[capacity]; slots = new InventorySlot[capacity];
@@ -249,7 +257,7 @@ namespace Barotrauma
slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * ((int)Math.Floor((double)i / slotsPerRow))); slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * ((int)Math.Floor((double)i / slotsPerRow)));
slots[i] = new InventorySlot(slotRect); slots[i] = new InventorySlot(slotRect);
slots[i].InteractRect = new Rectangle( slots[i].InteractRect = new Rectangle(
(int)(slots[i].Rect.X - spacing.X / 2 - 1), (int)(slots[i].Rect.Y - spacing.Y / 2 - 1), (int)(slots[i].Rect.X - spacing.X / 2 - 1), (int)(slots[i].Rect.Y - spacing.Y / 2 - 1),
(int)(slots[i].Rect.Width + spacing.X + 2), (int)(slots[i].Rect.Height + spacing.Y + 2)); (int)(slots[i].Rect.Width + spacing.X + 2), (int)(slots[i].Rect.Height + spacing.Y + 2));
if (slots[i].Rect.Width > slots[i].Rect.Height) if (slots[i].Rect.Width > slots[i].Rect.Height)
@@ -273,6 +281,22 @@ namespace Barotrauma
{ {
} }
public bool Movable()
{
return movableFrameRect.Size != Point.Zero;
}
public bool IsInventoryHoverAvailable(Character owner, ItemContainer container)
{
if (container == null && this is ItemInventory)
{
container = (this as ItemInventory).Container;
}
if (container == null) return false;
return owner.SelectedCharacter != null || !container.KeepOpenWhenEquipped || (!(owner is Character)) || !owner.HasEquippedItem(container.Item);
}
protected virtual bool HideSlot(int i) protected virtual bool HideSlot(int i)
{ {
return slots[i].Disabled || (hideEmptySlot[i] && Items[i] == null); return slots[i].Disabled || (hideEmptySlot[i] && Items[i] == null);
@@ -280,7 +304,7 @@ namespace Barotrauma
public virtual void Update(float deltaTime, Camera cam, bool subInventory = false) public virtual void Update(float deltaTime, Camera cam, bool subInventory = false)
{ {
if (slots == null || isSubInventory != subInventory || if (slots == null || isSubInventory != subInventory ||
(RectTransform != null && RectTransform.Rect != prevRect)) (RectTransform != null && RectTransform.Rect != prevRect))
{ {
CreateSlots(); CreateSlots();
@@ -341,12 +365,12 @@ namespace Barotrauma
} }
} }
slot.State = GUIComponent.ComponentState.None; slot.State = GUIComponent.ComponentState.None;
if (mouseOn && (draggingItem != null || selectedSlot == null || selectedSlot.Slot == slot)) if (mouseOn && (draggingItem != null || selectedSlot == null || selectedSlot.Slot == slot) && DraggingInventory == null)
// && // &&
//(highlightedSubInventories.Count == 0 || highlightedSubInventories.Contains(this) || highlightedSubInventorySlot?.Slot == slot || highlightedSubInventory.Owner == item)) //(highlightedSubInventories.Count == 0 || highlightedSubInventories.Contains(this) || highlightedSubInventorySlot?.Slot == slot || highlightedSubInventory.Owner == item))
{ {
slot.State = GUIComponent.ComponentState.Hover; slot.State = GUIComponent.ComponentState.Hover;
@@ -369,7 +393,7 @@ namespace Barotrauma
{ {
doubleClickedItem = item; doubleClickedItem = item;
} }
} }
} }
} }
@@ -384,7 +408,12 @@ namespace Barotrauma
return container.Inventory; return container.Inventory;
} }
float openState; protected virtual ItemInventory GetActiveEquippedSubInventory(int slotIndex)
{
return null;
}
public float OpenState;
public void UpdateSubInventory(float deltaTime, int slotIndex, Camera cam) public void UpdateSubInventory(float deltaTime, int slotIndex, Camera cam)
{ {
@@ -394,16 +423,45 @@ namespace Barotrauma
var container = item.GetComponent<ItemContainer>(); var container = item.GetComponent<ItemContainer>();
if (container == null || !container.DrawInventory) return; if (container == null || !container.DrawInventory) return;
var subInventory = container.Inventory; var subInventory = container.Inventory;
if (subInventory.slots == null) subInventory.CreateSlots(); if (subInventory.slots == null) subInventory.CreateSlots();
canMove = container.MovableFrame && !subInventory.IsInventoryHoverAvailable(Owner as Character, container) && subInventory.originalPos != Point.Zero;
if (canMove)
{
if (subInventory.movableFrameRect.Contains(PlayerInput.MousePosition) && PlayerInput.RightButtonClicked())
{
container.Inventory.savedPosition = container.Inventory.originalPos;
}
if (subInventory.movableFrameRect.Contains(PlayerInput.MousePosition) || (DraggingInventory != null && DraggingInventory == subInventory))
{
if (DraggingInventory == null)
{
if (PlayerInput.LeftButtonDown())
{
DraggingInventory = subInventory;
}
}
else if (PlayerInput.LeftButtonReleased())
{
DraggingInventory = null;
subInventory.savedPosition = PlayerInput.MousePosition.ToPoint();
}
else
{
subInventory.savedPosition = PlayerInput.MousePosition.ToPoint();
}
}
}
int itemCapacity = subInventory.Items.Length; int itemCapacity = subInventory.Items.Length;
var slot = slots[slotIndex]; var slot = slots[slotIndex];
int dir = slot.SubInventoryDir; int dir = slot.SubInventoryDir;
if (itemCapacity == 1 && false) if (itemCapacity == 1 && false)
{ {
Point slotSize = (slotSpriteRound.size * UIScale).ToPoint(); Point slotSize = (slotSpriteRound.size * UIScale).ToPoint();
subInventory.slots[0].Rect = subInventory.slots[0].Rect =
new Rectangle(slot.Rect.Center.X - slotSize.X / 2, dir > 0 ? slot.Rect.Bottom + 5 : slot.EquipButtonRect.Bottom + 5, slotSize.X, slotSize.Y); new Rectangle(slot.Rect.Center.X - slotSize.X / 2, dir > 0 ? slot.Rect.Bottom + 5 : slot.EquipButtonRect.Bottom + 5, slotSize.X, slotSize.Y);
subInventory.slots[0].InteractRect = subInventory.slots[0].Rect; subInventory.slots[0].InteractRect = subInventory.slots[0].Rect;
@@ -425,30 +483,43 @@ namespace Barotrauma
int width = (int)(subRect.Width * columns + spacing.X * (columns - 1)); int width = (int)(subRect.Width * columns + spacing.X * (columns - 1));
int startX = slot.Rect.Center.X - (int)(width / 2.0f); int startX = slot.Rect.Center.X - (int)(width / 2.0f);
//prevent the inventory from extending outside the left side of the screen
startX = Math.Max(startX, 10);
//same for the right side of the screen
startX -= Math.Max((startX + width) - GameMain.GraphicsWidth, 0);
subRect.X = startX;
int startY = dir < 0 ? int startY = dir < 0 ?
slot.EquipButtonRect.Y - subRect.Height - (int)(35 * UIScale) : slot.EquipButtonRect.Y - subRect.Height - (int)(35 * UIScale) :
slot.EquipButtonRect.Bottom + (int)(10 * UIScale); slot.EquipButtonRect.Bottom + (int)(10 * UIScale);
subRect.Y = startY;
if (canMove)
{
startX += subInventory.savedPosition.X - subInventory.originalPos.X;
startY += subInventory.savedPosition.Y - subInventory.originalPos.Y;
}
float totalHeight = itemCapacity / columns * (subRect.Height + spacing.Y); float totalHeight = itemCapacity / columns * (subRect.Height + spacing.Y);
subInventory.openState = subInventory.HideTimer >= 0.5f ? int padding = (int)(20 * UIScale);
Math.Min(subInventory.openState + deltaTime * 5.0f, 1.0f) :
Math.Max(subInventory.openState - deltaTime * 3.0f, 0.0f); //prevent the inventory from extending outside the left side of the screen
startX = Math.Max(startX, padding);
//same for the right side of the screen
startX -= Math.Max(startX + width - GameMain.GraphicsWidth + padding, 0);
//prevent the inventory from extending outside the top of the screen
startY = Math.Max(startY, (int)totalHeight - padding / 2);
//same for the bottom side of the screen
startY -= Math.Max(startY - GameMain.GraphicsHeight + padding * 2 + (canMove ? (int)(movableFrameRectHeight * UIScale) : 0), 0);
subRect.X = startX;
subRect.Y = startY;
subInventory.OpenState = subInventory.HideTimer >= 0.5f ?
Math.Min(subInventory.OpenState + deltaTime * 5.0f, 1.0f) :
Math.Max(subInventory.OpenState - deltaTime * 3.0f, 0.0f);
for (int i = 0; i < itemCapacity; i++) for (int i = 0; i < itemCapacity; i++)
{ {
subInventory.slots[i].Rect = subRect; subInventory.slots[i].Rect = subRect;
subInventory.slots[i].Rect.Location += new Point(0, (int)totalHeight * -dir); subInventory.slots[i].Rect.Location += new Point(0, (int)totalHeight * -dir);
subInventory.slots[i].DrawOffset = Vector2.SmoothStep( new Vector2(0, -50 * dir), new Vector2(0, totalHeight * dir), subInventory.openState); subInventory.slots[i].DrawOffset = Vector2.SmoothStep(new Vector2(0, -50 * dir), new Vector2(0, totalHeight * dir), subInventory.OpenState);
subInventory.slots[i].InteractRect = new Rectangle( subInventory.slots[i].InteractRect = new Rectangle(
(int)(subInventory.slots[i].Rect.X - spacing.X / 2 - 1), (int)(subInventory.slots[i].Rect.Y - spacing.Y / 2 - 1), (int)(subInventory.slots[i].Rect.X - spacing.X / 2 - 1), (int)(subInventory.slots[i].Rect.Y - spacing.Y / 2 - 1),
(int)(subInventory.slots[i].Rect.Width + spacing.X + 2), (int)(subInventory.slots[i].Rect.Height + spacing.Y + 2)); (int)(subInventory.slots[i].Rect.Width + spacing.X + 2), (int)(subInventory.slots[i].Rect.Height + spacing.Y + 2));
@@ -463,14 +534,19 @@ namespace Barotrauma
{ {
subRect.X = (int)(subInventory.slots[i].Rect.Right + spacing.X); subRect.X = (int)(subInventory.slots[i].Rect.Right + spacing.X);
} }
} }
if (canMove)
{
subInventory.movableFrameRect.X = subRect.X - (int)spacing.X;
subInventory.movableFrameRect.Y = subRect.Y + (int)(spacing.Y / 2f);
}
slots[slotIndex].State = GUIComponent.ComponentState.Hover; slots[slotIndex].State = GUIComponent.ComponentState.Hover;
} }
subInventory.isSubInventory = true; subInventory.isSubInventory = true;
subInventory.Update(deltaTime, cam, true); subInventory.Update(deltaTime, cam, true);
} }
public virtual void Draw(SpriteBatch spriteBatch, bool subInventory = false) public virtual void Draw(SpriteBatch spriteBatch, bool subInventory = false)
{ {
if (slots == null || isSubInventory != subInventory) return; if (slots == null || isSubInventory != subInventory) return;
@@ -497,7 +573,7 @@ namespace Barotrauma
{ {
if (Character.Controlled == null) return false; if (Character.Controlled == null) return false;
if (draggingItem != null) return true; if (draggingItem != null || DraggingInventory != null) return true;
if (Character.Controlled.Inventory != null) if (Character.Controlled.Inventory != null)
{ {
@@ -571,25 +647,32 @@ namespace Barotrauma
if (slotIndex < 0 || slotIndex >= Items.Length) return; if (slotIndex < 0 || slotIndex >= Items.Length) return;
#endif #endif
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; if (!canMove)
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
if (slots[slotIndex].SubInventoryDir > 0)
{ {
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle( Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
new Point(0, slots[slotIndex].Rect.Bottom), spriteBatch.End();
new Point(GameMain.GraphicsWidth, (int)Math.Max(GameMain.GraphicsHeight - slots[slotIndex].Rect.Bottom, 0))); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
if (slots[slotIndex].SubInventoryDir > 0)
{
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(
new Point(0, slots[slotIndex].Rect.Bottom),
new Point(GameMain.GraphicsWidth, (int)Math.Max(GameMain.GraphicsHeight - slots[slotIndex].Rect.Bottom, 0)));
}
else
{
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(
new Point(0, 0),
new Point(GameMain.GraphicsWidth, slots[slotIndex].Rect.Y));
}
container.Inventory.Draw(spriteBatch, true);
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred);
} }
else else
{ {
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle( container.Inventory.Draw(spriteBatch, true);
new Point(0, 0),
new Point(GameMain.GraphicsWidth, slots[slotIndex].Rect.Y));
} }
container.Inventory.Draw(spriteBatch, true);
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred);
container.InventoryBottomSprite?.Draw(spriteBatch, container.InventoryBottomSprite?.Draw(spriteBatch,
new Vector2(slots[slotIndex].Rect.Center.X, slots[slotIndex].Rect.Y) + slots[slotIndex].DrawOffset, new Vector2(slots[slotIndex].Rect.Center.X, slots[slotIndex].Rect.Y) + slots[slotIndex].DrawOffset,
@@ -597,10 +680,33 @@ namespace Barotrauma
container.InventoryTopSprite?.Draw(spriteBatch, container.InventoryTopSprite?.Draw(spriteBatch,
new Vector2( new Vector2(
slots[slotIndex].Rect.Center.X, slots[slotIndex].Rect.Center.X,
container.Inventory.slots[container.Inventory.slots.Length - 1].Rect.Y) + container.Inventory.slots[container.Inventory.slots.Length - 1].DrawOffset, container.Inventory.slots[container.Inventory.slots.Length - 1].Rect.Y) + container.Inventory.slots[container.Inventory.slots.Length - 1].DrawOffset,
0.0f, UIScale); 0.0f, UIScale);
if (container.MovableFrame && !IsInventoryHoverAvailable(Owner as Character, container))
{
if (positionUpdateQueued) // Wait a frame before updating the positioning of the container after a resolution change to have everything working
{
container.Inventory.originalPos = container.Inventory.savedPosition = container.Inventory.movableFrameRect.Center;
positionUpdateQueued = false;
}
if (container.Inventory.movableFrameRect.Size == Point.Zero || GUI.HasSizeChanged(prevScreenResolution, prevUIScale, prevHUDScale))
{
// Reset position
container.Inventory.savedPosition = container.Inventory.originalPos;
prevScreenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
prevUIScale = UIScale;
prevHUDScale = GUI.Scale;
int height = (int)(movableFrameRectHeight * UIScale);
container.Inventory.movableFrameRect = new Rectangle(container.Inventory.BackgroundFrame.X, container.Inventory.BackgroundFrame.Y - height, container.Inventory.BackgroundFrame.Width, height);
positionUpdateQueued = true;
}
GUI.DrawRectangle(spriteBatch, container.Inventory.movableFrameRect, movableFrameRectColor, true);
}
} }
public static void UpdateDragging() public static void UpdateDragging()
@@ -609,13 +715,13 @@ namespace Barotrauma
{ {
Character.Controlled.ClearInputs(); Character.Controlled.ClearInputs();
if (CharacterHealth.OpenHealthWindow != null && if (CharacterHealth.OpenHealthWindow != null &&
CharacterHealth.OpenHealthWindow.OnItemDropped(draggingItem, false)) CharacterHealth.OpenHealthWindow.OnItemDropped(draggingItem, false))
{ {
draggingItem = null; draggingItem = null;
return; return;
} }
if (selectedSlot == null) if (selectedSlot == null)
{ {
if (DraggingItemToWorld && if (DraggingItemToWorld &&
@@ -672,12 +778,23 @@ namespace Barotrauma
selectedSlot = null; selectedSlot = null;
} }
} }
protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot) protected static Rectangle GetSubInventoryHoverArea(SlotReference subSlot)
{ {
Rectangle hoverArea = subSlot.Slot.Rect; Rectangle hoverArea;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint(); if (!subSlot.Inventory.Movable())
hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect); {
hoverArea = subSlot.Slot.Rect;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Slot.EquipButtonRect);
}
else
{
hoverArea = subSlot.Inventory.BackgroundFrame;
hoverArea.Location += subSlot.Slot.DrawOffset.ToPoint();
hoverArea = Rectangle.Union(hoverArea, subSlot.Inventory.movableFrameRect);
}
if (subSlot.Inventory?.slots != null) if (subSlot.Inventory?.slots != null)
{ {
foreach (InventorySlot slot in subSlot.Inventory.slots) foreach (InventorySlot slot in subSlot.Inventory.slots)
@@ -697,12 +814,17 @@ namespace Barotrauma
hoverArea.Height -= over; hoverArea.Height -= over;
} }
} }
hoverArea.Inflate(10, 10);
float inflateAmount = 10 * UIScale;
hoverArea.Inflate(inflateAmount, inflateAmount);
return hoverArea; return hoverArea;
} }
public static void DrawFront(SpriteBatch spriteBatch) public static void DrawFront(SpriteBatch spriteBatch)
{ {
if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) return;
foreach (var slot in highlightedSubInventorySlots) foreach (var slot in highlightedSubInventorySlots)
{ {
int slotIndex = Array.IndexOf(slot.ParentInventory.slots, slot.Slot); int slotIndex = Array.IndexOf(slot.ParentInventory.slots, slot.Slot);
@@ -710,7 +832,7 @@ namespace Barotrauma
{ {
slot.ParentInventory.DrawSubInventory(spriteBatch, slotIndex); slot.ParentInventory.DrawSubInventory(spriteBatch, slotIndex);
} }
} }
if (draggingItem != null) if (draggingItem != null)
{ {
@@ -727,7 +849,7 @@ namespace Barotrauma
if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null)
{ {
var shadowSprite = GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0]; var shadowSprite = GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0];
string toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") : string toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") :
Character.Controlled.FocusedItem != null ? Character.Controlled.FocusedItem != null ?
TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, true) : TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, true) :
TextManager.Get("DropItem"); TextManager.Get("DropItem");
@@ -807,7 +929,7 @@ namespace Barotrauma
{ {
Rectangle rect = slot.Rect; Rectangle rect = slot.Rect;
rect.Location += slot.DrawOffset.ToPoint(); rect.Location += slot.DrawOffset.ToPoint();
if (slot.HighlightColor.A > 0) if (slot.HighlightColor.A > 0)
{ {
float inflateAmount = (slot.HighlightColor.A / 255.0f) * slot.HighlightScaleUpAmount * 0.5f; float inflateAmount = (slot.HighlightColor.A / 255.0f) * slot.HighlightScaleUpAmount * 0.5f;
@@ -860,7 +982,7 @@ namespace Barotrauma
Rectangle containedIndicatorArea = new Rectangle(rect.X, Rectangle containedIndicatorArea = new Rectangle(rect.X,
dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - ContainedIndicatorHeight, rect.Width, ContainedIndicatorHeight); dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - ContainedIndicatorHeight, rect.Width, ContainedIndicatorHeight);
containedIndicatorArea.Inflate(-4, 0); containedIndicatorArea.Inflate(-4, 0);
if (itemContainer.ContainedStateIndicator?.Texture == null) if (itemContainer.ContainedStateIndicator?.Texture == null)
{ {
containedIndicatorArea.Inflate(0, -2); containedIndicatorArea.Inflate(0, -2);
@@ -882,7 +1004,7 @@ namespace Barotrauma
} }
indicatorSprite.Draw(spriteBatch, containedIndicatorArea.Center.ToVector2(), indicatorSprite.Draw(spriteBatch, containedIndicatorArea.Center.ToVector2(),
(inventory != null && inventory.Locked) ? Color.DarkGray * 0.5f : Color.DarkGray * 0.9f, (inventory != null && inventory.Locked) ? Color.DarkGray * 0.5f : Color.DarkGray * 0.9f,
origin: indicatorSprite.size / 2, origin: indicatorSprite.size / 2,
rotate: 0.0f, rotate: 0.0f,
scale: indicatorScale); scale: indicatorScale);
@@ -944,19 +1066,19 @@ namespace Barotrauma
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
} }
if (inventory != null && if (inventory != null &&
!inventory.Locked && !inventory.Locked &&
Character.Controlled?.Inventory == inventory && Character.Controlled?.Inventory == inventory &&
slot.QuickUseKey != Keys.None) slot.QuickUseKey != Keys.None)
{ {
GUI.DrawString(spriteBatch, rect.Location.ToVector2(), GUI.DrawString(spriteBatch, rect.Location.ToVector2(),
slot.QuickUseKey.ToString().Substring(1, 1), slot.QuickUseKey.ToString().Substring(1, 1),
item == null || !drawItem ? Color.Gray : Color.White, item == null || !drawItem ? Color.Gray : Color.White,
Color.Black * 0.8f); Color.Black * 0.8f);
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
receivedItemIDs = new ushort[capacity]; receivedItemIDs = new ushort[capacity];
@@ -1036,7 +1158,7 @@ namespace Barotrauma
receivedItemIDs = null; receivedItemIDs = null;
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
SharedWrite(msg, extraData); SharedWrite(msg, extraData);
syncItemsDelay = 1.0f; syncItemsDelay = 1.0f;
@@ -1,7 +1,6 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
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;
@@ -432,23 +431,6 @@ namespace Barotrauma
if (!animate) { continue; } if (!animate) { continue; }
spriteState.OffsetState += deltaTime; spriteState.OffsetState += deltaTime;
spriteState.RotationState += deltaTime; spriteState.RotationState += deltaTime;
bool ConditionalMatches(PropertyConditional conditional)
{
if (string.IsNullOrEmpty(conditional.TargetItemComponentName))
{
if (!conditional.Matches(this)) { return false; }
}
else
{
foreach (ItemComponent component in components)
{
if (component.Name != conditional.TargetItemComponentName) { continue; }
if (!conditional.Matches(component)) { return false; }
}
}
return true;
}
} }
} }
} }
@@ -656,10 +638,11 @@ namespace Barotrauma
List<Rectangle> disallowedAreas = new List<Rectangle>(); List<Rectangle> disallowedAreas = new List<Rectangle>();
if (GameMain.GameSession?.CrewManager != null && Screen.Selected == GameMain.GameScreen) if (GameMain.GameSession?.CrewManager != null && Screen.Selected == GameMain.GameScreen)
{ {
int disallowedPadding = (int)(50 * GUI.Scale);
disallowedAreas.Add(GameMain.GameSession.CrewManager.GetCharacterListArea()); disallowedAreas.Add(GameMain.GameSession.CrewManager.GetCharacterListArea());
disallowedAreas.Add(new Rectangle( disallowedAreas.Add(new Rectangle(
HUDLayoutSettings.ChatBoxArea.X - 50, HUDLayoutSettings.ChatBoxArea.Y, HUDLayoutSettings.ChatBoxArea.X - disallowedPadding, HUDLayoutSettings.ChatBoxArea.Y,
HUDLayoutSettings.ChatBoxArea.Width + 50, HUDLayoutSettings.ChatBoxArea.Height)); HUDLayoutSettings.ChatBoxArea.Width + disallowedPadding, HUDLayoutSettings.ChatBoxArea.Height));
} }
GUI.PreventElementOverlap(elementsToMove, disallowedAreas, GUI.PreventElementOverlap(elementsToMove, disallowedAreas,
@@ -798,7 +781,7 @@ namespace Barotrauma
{ {
if (ic is Repairable repairable) if (ic is Repairable repairable)
{ {
if (Condition < repairable.ShowRepairUIThreshold) if (ConditionPercentage < repairable.ShowRepairUIThreshold)
{ {
color = Color.Cyan; color = Color.Cyan;
} }
@@ -858,7 +841,7 @@ namespace Barotrauma
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (type == ServerNetObject.ENTITY_POSITION) if (type == ServerNetObject.ENTITY_POSITION)
{ {
@@ -868,7 +851,7 @@ namespace Barotrauma
NetEntityEvent.Type eventType = NetEntityEvent.Type eventType =
(NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); (NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
switch (eventType) switch (eventType)
{ {
case NetEntityEvent.Type.ComponentState: case NetEntityEvent.Type.ComponentState:
@@ -939,7 +922,7 @@ namespace Barotrauma
} }
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type)) if (extraData == null || extraData.Length == 0 || !(extraData[0] is NetEntityEvent.Type))
{ {
@@ -947,17 +930,17 @@ namespace Barotrauma
} }
NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0]; NetEntityEvent.Type eventType = (NetEntityEvent.Type)extraData[0];
msg.WriteRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType); msg.WriteRangedIntegerDeprecated(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1, (int)eventType);
switch (eventType) switch (eventType)
{ {
case NetEntityEvent.Type.ComponentState: case NetEntityEvent.Type.ComponentState:
int componentIndex = (int)extraData[1]; int componentIndex = (int)extraData[1];
msg.WriteRangedInteger(0, components.Count - 1, componentIndex); msg.WriteRangedIntegerDeprecated(0, components.Count - 1, componentIndex);
(components[componentIndex] as IClientSerializable).ClientWrite(msg, extraData); (components[componentIndex] as IClientSerializable).ClientWrite(msg, extraData);
break; break;
case NetEntityEvent.Type.InventoryState: case NetEntityEvent.Type.InventoryState:
int containerIndex = (int)extraData[1]; int containerIndex = (int)extraData[1];
msg.WriteRangedInteger(0, components.Count - 1, containerIndex); msg.WriteRangedIntegerDeprecated(0, components.Count - 1, containerIndex);
(components[containerIndex] as ItemContainer).Inventory.ClientWrite(msg, extraData); (components[containerIndex] as ItemContainer).Inventory.ClientWrite(msg, extraData);
break; break;
case NetEntityEvent.Type.Treatment: case NetEntityEvent.Type.Treatment:
@@ -1010,7 +993,7 @@ namespace Barotrauma
rect.Y = (int)(displayPos.Y + rect.Height / 2.0f); rect.Y = (int)(displayPos.Y + rect.Height / 2.0f);
} }
public void ClientReadPosition(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientReadPosition(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (body == null) if (body == null)
{ {
@@ -1078,7 +1061,7 @@ namespace Barotrauma
GameMain.Client.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ComponentState, index }); GameMain.Client.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.ComponentState, index });
} }
public static Item ReadSpawnData(NetBuffer msg, bool spawn = true) public static Item ReadSpawnData(IReadMessage msg, bool spawn = true)
{ {
string itemName = msg.ReadString(); string itemName = msg.ReadString();
string itemIdentifier = msg.ReadString(); string itemIdentifier = msg.ReadString();
@@ -1140,23 +1123,32 @@ namespace Barotrauma
} }
Inventory inventory = null; Inventory inventory = null;
if (inventoryId > 0)
var inventoryOwner = FindEntityByID(inventoryId);
if (inventoryOwner != null)
{ {
if (inventoryOwner is Character) var inventoryOwner = FindEntityByID(inventoryId);
if (inventoryOwner is Character character)
{ {
inventory = (inventoryOwner as Character).Inventory; inventory = character.Inventory;
} }
else if (inventoryOwner is Item) else if (inventoryOwner is Item parentItem)
{ {
if ((inventoryOwner as Item).components[itemContainerIndex] is ItemContainer container) if (itemContainerIndex < 0 || itemContainerIndex >= parentItem.components.Count)
{
string errorMsg = "Failed to spawn item \"" + (itemIdentifier ?? "null") +
"\" in the inventory of \"" + parentItem.prefab.Identifier + "\" (component index out of range). Index: " + itemContainerIndex + ", components: " + parentItem.components.Count + ".";
GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ?? "null") + (itemIdentifier ?? "null"),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg);
DebugConsole.ThrowError(errorMsg);
}
else if (parentItem.components[itemContainerIndex] is ItemContainer container)
{ {
inventory = container.Inventory; inventory = container.Inventory;
} }
} }
} }
var item = new Item(itemPrefab, pos, sub) var item = new Item(itemPrefab, pos, sub)
{ {
ID = itemId ID = itemId
@@ -1197,4 +1189,4 @@ namespace Barotrauma
} }
} }
} }
} }
@@ -1,7 +1,6 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
@@ -528,14 +527,14 @@ namespace Barotrauma
} }
} }
public void ClientWrite(NetBuffer msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8); msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
msg.Write(FireSources.Count > 0); msg.Write(FireSources.Count > 0);
if (FireSources.Count > 0) if (FireSources.Count > 0)
{ {
msg.WriteRangedInteger(0, 16, Math.Min(FireSources.Count, 16)); msg.WriteRangedIntegerDeprecated(0, 16, Math.Min(FireSources.Count, 16));
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++) for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{ {
var fireSource = FireSources[i]; var fireSource = FireSources[i];
@@ -550,7 +549,7 @@ namespace Barotrauma
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer message, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
{ {
remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume; remoteWaterVolume = message.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8); remoteOxygenPercentage = message.ReadRangedSingle(0.0f, 100.0f, 8);
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
@@ -95,7 +94,7 @@ namespace Barotrauma
renderer.DrawBackground(spriteBatch, cam, levelObjectManager, backgroundCreatureManager); renderer.DrawBackground(spriteBatch, cam, levelObjectManager, backgroundCreatureManager);
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
foreach (LevelWall levelWall in extraWalls) foreach (LevelWall levelWall in extraWalls)
{ {
@@ -1,12 +1,12 @@
using Barotrauma.Lights; using Barotrauma.Lights;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.SpriteDeformations; using Barotrauma.SpriteDeformations;
using Lidgren.Network;
using System.Linq; using System.Linq;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
@@ -267,7 +267,7 @@ namespace Barotrauma
} }
} }
public void ClientRead(NetBuffer msg) public void ClientRead(IReadMessage msg)
{ {
for (int i = 0; i < Triggers.Count; i++) for (int i = 0; i < Triggers.Count; i++)
{ {
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -165,7 +164,7 @@ namespace Barotrauma
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
int objIndex = msg.ReadRangedInteger(0, objects.Count); int objIndex = msg.ReadRangedInteger(0, objects.Count);
objects[objIndex].ClientRead(msg); objects[objIndex].ClientRead(msg);
@@ -1,10 +1,10 @@
using Lidgren.Network; using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
partial class LevelTrigger partial class LevelTrigger
{ {
public void ClientRead(NetBuffer msg) public void ClientRead(IReadMessage msg)
{ {
if (ForceFluctuationStrength > 0.0f) if (ForceFluctuationStrength > 0.0f)
{ {
@@ -382,7 +382,7 @@ namespace Barotrauma
} }
else else
{ {
selectedList = newSelection; selectedList = new List<MapEntity>(newSelection);
//selectedList.Clear(); //selectedList.Clear();
//newSelection.ForEach(e => AddSelection(e)); //newSelection.ForEach(e => AddSelection(e));
foreach (var entity in newSelection) foreach (var entity in newSelection)
@@ -4,7 +4,6 @@ using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Contacts; using FarseerPhysics.Dynamics.Contacts;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -346,12 +345,23 @@ namespace Barotrauma
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
for (int i = 0; i < Sections.Length; i++) byte sectionCount = msg.ReadByte();
if (sectionCount != Sections.Length)
{
string errorMsg = $"Error while reading a network event for the structure \"{Name}\". Section count does not match (server: {sectionCount} client: {Sections.Length})";
DebugConsole.NewMessage(errorMsg, Color.Red);
GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
for (int i = 0; i < sectionCount; i++)
{ {
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health; float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health;
SetDamage(i, damage); if (i < Sections.Length)
{
SetDamage(i, damage);
}
} }
} }
} }
@@ -2,7 +2,6 @@
using Barotrauma.RuinGeneration; using Barotrauma.RuinGeneration;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using FarseerPhysics; using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
@@ -514,7 +513,7 @@ namespace Barotrauma
} }
} }
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Name); var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Name);
msg.ReadPadBits(); msg.ReadPadBits();
@@ -1,5 +1,4 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Lidgren.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@@ -17,7 +16,7 @@ namespace Barotrauma.Networking
} }
} }
partial class BanList public partial class BanList
{ {
private GUIComponent banFrame; private GUIComponent banFrame;
@@ -129,7 +128,7 @@ namespace Barotrauma.Networking
return true; return true;
} }
public void ClientAdminRead(NetBuffer incMsg) public void ClientAdminRead(IReadMessage incMsg)
{ {
bool hasPermission = incMsg.ReadBoolean(); bool hasPermission = incMsg.ReadBoolean();
if (!hasPermission) if (!hasPermission)
@@ -142,8 +141,8 @@ namespace Barotrauma.Networking
incMsg.ReadPadBits(); incMsg.ReadPadBits();
bannedPlayers.Clear(); bannedPlayers.Clear();
Int32 bannedPlayerCount = incMsg.ReadVariableInt32(); UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
for (int i = 0; i < bannedPlayerCount; i++) for (int i = 0; i < (int)bannedPlayerCount; i++)
{ {
string name = incMsg.ReadString(); string name = incMsg.ReadString();
UInt16 uniqueIdentifier = incMsg.ReadUInt16(); UInt16 uniqueIdentifier = incMsg.ReadUInt16();
@@ -172,7 +171,7 @@ namespace Barotrauma.Networking
} }
} }
public void ClientAdminWrite(NetBuffer outMsg) public void ClientAdminWrite(IWriteMessage outMsg)
{ {
outMsg.Write((UInt16)localRemovedBans.Count); outMsg.Write((UInt16)localRemovedBans.Count);
foreach (UInt16 uniqueId in localRemovedBans) foreach (UInt16 uniqueId in localRemovedBans)
@@ -1,12 +1,11 @@
using Lidgren.Network; using System;
using System;
using System.Linq; using System.Linq;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
partial class ChatMessage partial class ChatMessage
{ {
public virtual void ClientWrite(NetOutgoingMessage msg) public virtual void ClientWrite(IWriteMessage msg)
{ {
msg.Write((byte)ClientNetObject.CHAT_MESSAGE); msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID); msg.Write(NetStateID);
@@ -14,7 +13,7 @@ namespace Barotrauma.Networking
msg.Write(Text); msg.Write(Text);
} }
public static void ClientRead(NetIncomingMessage msg) public static void ClientRead(IReadMessage msg)
{ {
UInt16 ID = msg.ReadUInt16(); UInt16 ID = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte(); ChatMessageType type = (ChatMessageType)msg.ReadByte();
@@ -37,7 +36,11 @@ namespace Barotrauma.Networking
} }
} }
if (type == ChatMessageType.Order) if (type == ChatMessageType.ServerMessageBox)
{
txt = TextManager.GetServerMessage(txt);
}
else if (type == ChatMessageType.Order)
{ {
int orderIndex = msg.ReadByte(); int orderIndex = msg.ReadByte();
UInt16 targetCharacterID = msg.ReadUInt16(); UInt16 targetCharacterID = msg.ReadUInt16();
@@ -63,17 +66,20 @@ namespace Barotrauma.Networking
} }
txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption); txt = order.GetChatMessage(targetCharacter?.Name, senderCharacter?.CurrentHull?.DisplayName, givingOrderToSelf: targetCharacter == senderCharacter, orderOption: orderOption);
if (order.TargetAllCharacters) if (GameMain.Client.GameStarted)
{ {
GameMain.GameSession?.CrewManager?.AddOrder( if (order.TargetAllCharacters)
new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter), {
order.Prefab.FadeOutTime); GameMain.GameSession?.CrewManager?.AddOrder(
} new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter),
else if (targetCharacter != null) order.Prefab.FadeOutTime);
{ }
targetCharacter.SetOrder( else if (targetCharacter != null)
new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter), {
orderOption, senderCharacter); targetCharacter.SetOrder(
new Order(order.Prefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: senderCharacter),
orderOption, senderCharacter);
}
} }
if (NetIdUtils.IdMoreRecent(ID, LastID)) if (NetIdUtils.IdMoreRecent(ID, LastID))
@@ -90,7 +96,12 @@ namespace Barotrauma.Networking
switch (type) switch (type)
{ {
case ChatMessageType.MessageBox: case ChatMessageType.MessageBox:
new GUIMessageBox("", txt); case ChatMessageType.ServerMessageBox:
//only show the message box if the text differs from the text in the currently visible box
if ((GUIMessageBox.VisibleBox as GUIMessageBox)?.Text?.Text != txt)
{
new GUIMessageBox("", txt);
}
break; break;
case ChatMessageType.Console: case ChatMessageType.Console:
DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]); DebugConsole.NewMessage(txt, MessageColor[(int)ChatMessageType.Console]);
@@ -9,6 +9,7 @@ namespace Barotrauma.Networking
struct TempClient struct TempClient
{ {
public string Name; public string Name;
public UInt64 SteamID;
public byte ID; public byte ID;
public UInt16 CharacterID; public UInt16 CharacterID;
public bool Muted; public bool Muted;
@@ -76,7 +77,7 @@ namespace Barotrauma.Networking
VoipQueue = null; VoipSound = null; VoipQueue = null; VoipSound = null;
if (ID == GameMain.Client.ID) return; if (ID == GameMain.Client.ID) return;
VoipQueue = new VoipQueue(ID, false, true); VoipQueue = new VoipQueue(ID, false, true);
GameMain.Client.VoipClient.RegisterQueue(VoipQueue); GameMain.Client?.VoipClient?.RegisterQueue(VoipQueue);
VoipSound = null; VoipSound = null;
} }
@@ -4,7 +4,7 @@ namespace Barotrauma
{ {
partial class EntitySpawner : Entity, IServerSerializable partial class EntitySpawner : Entity, IServerSerializable
{ {
public void ClientRead(ServerNetObject type, Lidgren.Network.NetBuffer message, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage message, float sendingTime)
{ {
bool remove = message.ReadBoolean(); bool remove = message.ReadBoolean();
@@ -1,5 +1,4 @@
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.IO; using System.IO;
@@ -25,13 +24,13 @@ namespace Barotrauma.Networking
private set; private set;
} }
public ulong FileSize public int FileSize
{ {
get; get;
set; set;
} }
public ulong Received public int Received
{ {
get; get;
private set; private set;
@@ -72,15 +71,15 @@ namespace Barotrauma.Networking
private set; private set;
} }
public NetConnection Connection public NetworkConnection Connection
{ {
get; get;
private set; private set;
} }
public int SequenceChannel; public int ID;
public FileTransferIn(NetConnection connection, string filePath, FileTransferType fileType) public FileTransferIn(NetworkConnection connection, string filePath, FileTransferType fileType)
{ {
FilePath = filePath; FilePath = filePath;
FileName = Path.GetFileName(FilePath); FileName = Path.GetFileName(FilePath);
@@ -105,17 +104,16 @@ namespace Barotrauma.Networking
TimeStarted = Environment.TickCount; TimeStarted = Environment.TickCount;
} }
public void ReadBytes(NetIncomingMessage inc) public void ReadBytes(IReadMessage inc, int bytesToRead)
{ {
int bytesToRead = inc.LengthBytes - inc.PositionInBytes; if (Received + bytesToRead > FileSize)
if (Received + (ulong)(bytesToRead) > FileSize)
{ {
//strip out excess bytes //strip out excess bytes
bytesToRead -= (int)((Received + (ulong)bytesToRead) - FileSize); bytesToRead -= Received + bytesToRead - FileSize;
} }
byte[] all = inc.ReadBytes(bytesToRead); byte[] all = inc.ReadBytes(bytesToRead);
Received += (ulong)all.Length; Received += all.Length;
WriteStream.Write(all, 0, all.Length); WriteStream.Write(all, 0, all.Length);
int passed = Environment.TickCount - TimeStarted; int passed = Environment.TickCount - TimeStarted;
@@ -162,6 +160,7 @@ namespace Barotrauma.Networking
public TransferInDelegate OnTransferFailed; public TransferInDelegate OnTransferFailed;
private List<FileTransferIn> activeTransfers; private List<FileTransferIn> activeTransfers;
private List<Pair<int, double>> finishedTransfers;
private Dictionary<FileTransferType, string> downloadFolders = new Dictionary<FileTransferType, string>() private Dictionary<FileTransferType, string> downloadFolders = new Dictionary<FileTransferType, string>()
{ {
@@ -177,9 +176,10 @@ namespace Barotrauma.Networking
public FileReceiver() public FileReceiver()
{ {
activeTransfers = new List<FileTransferIn>(); activeTransfers = new List<FileTransferIn>();
finishedTransfers = new List<Pair<int, double>>();
} }
public void ReadMessage(NetIncomingMessage inc) public void ReadMessage(IReadMessage inc)
{ {
System.Diagnostics.Debug.Assert(!activeTransfers.Any(t => System.Diagnostics.Debug.Assert(!activeTransfers.Any(t =>
t.Status == FileTransferStatus.Error || t.Status == FileTransferStatus.Error ||
@@ -187,26 +187,38 @@ namespace Barotrauma.Networking
t.Status == FileTransferStatus.Finished), "List of active file transfers contains entires that should have been removed"); t.Status == FileTransferStatus.Finished), "List of active file transfers contains entires that should have been removed");
byte transferMessageType = inc.ReadByte(); byte transferMessageType = inc.ReadByte();
switch (transferMessageType) switch (transferMessageType)
{ {
case (byte)FileTransferMessageType.Initiate: case (byte)FileTransferMessageType.Initiate:
{ {
var existingTransfer = activeTransfers.Find(t => t.SequenceChannel == inc.SequenceChannel); byte transferId = inc.ReadByte();
if (existingTransfer != null) var existingTransfer = activeTransfers.Find(t => t.ID == transferId);
{ finishedTransfers.RemoveAll(t => t.First == transferId);
GameMain.Client.CancelFileTransfer(inc.SequenceChannel);
DebugConsole.ThrowError("File transfer error: file transfer initiated on a sequence channel that's already in use");
return;
}
byte fileType = inc.ReadByte(); byte fileType = inc.ReadByte();
ushort chunkLen = inc.ReadUInt16(); //ushort chunkLen = inc.ReadUInt16();
ulong fileSize = inc.ReadUInt64(); int fileSize = inc.ReadInt32();
string fileName = inc.ReadString(); string fileName = inc.ReadString();
if (existingTransfer != null)
{
if (fileType != (byte)existingTransfer.FileType ||
fileSize != existingTransfer.FileSize ||
fileName != existingTransfer.FileName)
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: file transfer initiated with an ID that's already in use");
}
else //resend acknowledgement packet
{
GameMain.Client.UpdateFileTransfer(transferId, 0);
}
return;
}
if (!ValidateInitialData(fileType, fileName, fileSize, out string errorMsg)) if (!ValidateInitialData(fileType, fileName, fileSize, out string errorMsg))
{ {
GameMain.Client.CancelFileTransfer(inc.SequenceChannel); GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer failed (" + errorMsg + ")"); DebugConsole.ThrowError("File transfer failed (" + errorMsg + ")");
return; return;
} }
@@ -216,7 +228,7 @@ namespace Barotrauma.Networking
DebugConsole.Log("Received file transfer initiation message: "); DebugConsole.Log("Received file transfer initiation message: ");
DebugConsole.Log(" File: " + fileName); DebugConsole.Log(" File: " + fileName);
DebugConsole.Log(" Size: " + fileSize); DebugConsole.Log(" Size: " + fileSize);
DebugConsole.Log(" Sequence channel: " + inc.SequenceChannel); DebugConsole.Log(" ID: " + transferId);
} }
string downloadFolder = downloadFolders[(FileTransferType)fileType]; string downloadFolder = downloadFolders[(FileTransferType)fileType];
@@ -233,9 +245,9 @@ namespace Barotrauma.Networking
} }
} }
FileTransferIn newTransfer = new FileTransferIn(inc.SenderConnection, Path.Combine(downloadFolder, fileName), (FileTransferType)fileType) FileTransferIn newTransfer = new FileTransferIn(inc.Sender, Path.Combine(downloadFolder, fileName), (FileTransferType)fileType)
{ {
SequenceChannel = inc.SequenceChannel, ID = transferId,
Status = FileTransferStatus.Receiving, Status = FileTransferStatus.Receiving,
FileSize = fileSize FileSize = fileSize
}; };
@@ -257,7 +269,7 @@ namespace Barotrauma.Networking
else else
{ {
DebugConsole.NewMessage("Failed to initiate a file transfer {" + e.Message + "}", Color.Red); DebugConsole.NewMessage("Failed to initiate a file transfer {" + e.Message + "}", Color.Red);
GameMain.Client.CancelFileTransfer(inc.SequenceChannel); GameMain.Client.CancelFileTransfer(transferId);
newTransfer.Status = FileTransferStatus.Error; newTransfer.Status = FileTransferStatus.Error;
OnTransferFailed(newTransfer); OnTransferFailed(newTransfer);
return; return;
@@ -265,10 +277,13 @@ namespace Barotrauma.Networking
} }
} }
activeTransfers.Add(newTransfer); activeTransfers.Add(newTransfer);
GameMain.Client.UpdateFileTransfer(transferId, 0); //send acknowledgement packet
} }
break; break;
case (byte)FileTransferMessageType.TransferOnSameMachine: case (byte)FileTransferMessageType.TransferOnSameMachine:
{ {
byte transferId = inc.ReadByte();
byte fileType = inc.ReadByte(); byte fileType = inc.ReadByte();
string filePath = inc.ReadString(); string filePath = inc.ReadString();
@@ -276,19 +291,19 @@ namespace Barotrauma.Networking
{ {
DebugConsole.Log("Received file transfer message on the same machine: "); DebugConsole.Log("Received file transfer message on the same machine: ");
DebugConsole.Log(" File: " + filePath); DebugConsole.Log(" File: " + filePath);
DebugConsole.Log(" Sequence channel: " + inc.SequenceChannel); DebugConsole.Log(" ID: " + transferId);
} }
if (!File.Exists(filePath)) if (!File.Exists(filePath))
{ {
DebugConsole.ThrowError("File transfer on the same machine failed, file \"" + filePath + "\" not found."); DebugConsole.ThrowError("File transfer on the same machine failed, file \"" + filePath + "\" not found.");
GameMain.Client.CancelFileTransfer(inc.SequenceChannel); GameMain.Client.CancelFileTransfer(transferId);
return; return;
} }
FileTransferIn directTransfer = new FileTransferIn(inc.SenderConnection, filePath, (FileTransferType)fileType) FileTransferIn directTransfer = new FileTransferIn(inc.Sender, filePath, (FileTransferType)fileType)
{ {
SequenceChannel = inc.SequenceChannel, ID = transferId,
Status = FileTransferStatus.Finished, Status = FileTransferStatus.Finished,
FileSize = 0 FileSize = 0
}; };
@@ -296,79 +311,104 @@ namespace Barotrauma.Networking
} }
break; break;
case (byte)FileTransferMessageType.Data: case (byte)FileTransferMessageType.Data:
var activeTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == inc.SequenceChannel);
if (activeTransfer == null)
{ {
GameMain.Client.CancelFileTransfer(inc.SequenceChannel); byte transferId = inc.ReadByte();
DebugConsole.ThrowError("File transfer error: received data without a transfer initiation message");
return;
}
//allow one extra byte at the end for the 0 that identifies non-compressed messages var activeTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (activeTransfer.Received + (ulong)(inc.LengthBytes - inc.PositionInBytes) > activeTransfer.FileSize + 1) if (activeTransfer == null)
{
GameMain.Client.CancelFileTransfer(inc.SequenceChannel);
DebugConsole.ThrowError("File transfer error: Received more data than expected (total received: " + activeTransfer.Received +
", msg received: " + (inc.LengthBytes - inc.PositionInBytes) +
", msg length: " + inc.LengthBytes +
", msg read: " + inc.PositionInBytes +
", filesize: " + activeTransfer.FileSize);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer);
return;
}
try
{
activeTransfer.ReadBytes(inc);
}
catch (Exception e)
{
GameMain.Client.CancelFileTransfer(inc.SequenceChannel);
DebugConsole.ThrowError("File transfer error: " + e.Message);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer, true);
return;
}
if (activeTransfer.Status == FileTransferStatus.Finished)
{
activeTransfer.Dispose();
if (ValidateReceivedData(activeTransfer, out string errorMessage))
{ {
StopTransfer(activeTransfer); //it's possible for the server to send some extra data
OnFinished(activeTransfer); //before it acknowledges that the download is finished,
//so let's suppress the error message in that case
finishedTransfers.RemoveAll(t => t.Second + 5.0 < Timing.TotalTime);
if (!finishedTransfers.Any(t => t.First == transferId))
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: received data without a transfer initiation message");
}
return;
} }
else
{
new GUIMessageBox("File transfer aborted", errorMessage);
int offset = inc.ReadInt32();
if (offset != activeTransfer.Received)
{
if (offset < activeTransfer.Received)
{
GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received);
}
return;
}
int bytesToRead = inc.ReadUInt16();
if (activeTransfer.Received + bytesToRead > activeTransfer.FileSize)
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: Received more data than expected (total received: " + activeTransfer.Received +
", msg received: " + (inc.LengthBytes - inc.BytePosition) +
", msg length: " + inc.LengthBytes +
", msg read: " + inc.BytePosition +
", filesize: " + activeTransfer.FileSize);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer);
return;
}
try
{
activeTransfer.ReadBytes(inc, bytesToRead);
}
catch (Exception e)
{
GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: " + e.Message);
activeTransfer.Status = FileTransferStatus.Error; activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer, true); StopTransfer(activeTransfer, true);
return;
}
if (activeTransfer.Status == FileTransferStatus.Finished)
{
GameMain.Client.UpdateFileTransfer(activeTransfer.ID, activeTransfer.Received, true);
activeTransfer.Dispose();
if (ValidateReceivedData(activeTransfer, out string errorMessage))
{
finishedTransfers.Add(new Pair<int, double>(transferId, Timing.TotalTime));
StopTransfer(activeTransfer);
OnFinished(activeTransfer);
}
else
{
new GUIMessageBox("File transfer aborted", errorMessage);
activeTransfer.Status = FileTransferStatus.Error;
StopTransfer(activeTransfer, true);
}
} }
} }
break; break;
case (byte)FileTransferMessageType.Cancel: case (byte)FileTransferMessageType.Cancel:
byte sequenceChannel = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.SenderConnection && t.SequenceChannel == sequenceChannel);
if (matchingTransfer != null)
{ {
new GUIMessageBox("File transfer cancelled", "The server has cancelled the transfer of the file \"" + matchingTransfer.FileName + "\"."); byte transferId = inc.ReadByte();
StopTransfer(matchingTransfer); var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
if (matchingTransfer != null)
{
new GUIMessageBox("File transfer cancelled", "The server has cancelled the transfer of the file \"" + matchingTransfer.FileName + "\".");
StopTransfer(matchingTransfer);
}
break;
} }
break;
} }
} }
private bool ValidateInitialData(byte type, string fileName, ulong fileSize, out string errorMessage) private bool ValidateInitialData(byte type, string fileName, int fileSize, out string errorMessage)
{ {
errorMessage = ""; errorMessage = "";
if (fileSize > MaxFileSize) if (fileSize > MaxFileSize)
{ {
errorMessage = "File too large (" + MathUtils.GetBytesReadable((long)fileSize) + ")"; errorMessage = "File too large (" + MathUtils.GetBytesReadable(fileSize) + ")";
return false; return false;
} }
File diff suppressed because it is too large Load Diff
@@ -1,7 +1,7 @@
using System; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text; using System.Text;
using Microsoft.Xna.Framework;
namespace Barotrauma namespace Barotrauma
{ {
@@ -10,6 +10,10 @@ namespace Barotrauma
public void CreateSettingsFrame(GUIComponent parent) public void CreateSettingsFrame(GUIComponent parent)
{ {
CreateLabeledSlider(parent, 0.0f, 40.0f, 1.0f, "KickBanThreshold"); CreateLabeledSlider(parent, 0.0f, 40.0f, 1.0f, "KickBanThreshold");
if (TextManager.ContainsTag("Karma.KicksBeforeBan"))
{
CreateLabeledNumberInput(parent, 0, 10, "KicksBeforeBan");
}
CreateLabeledSlider(parent, 0.0f, 50.0f, 1.0f, "HerpesThreshold"); CreateLabeledSlider(parent, 0.0f, 50.0f, 1.0f, "HerpesThreshold");
CreateLabeledSlider(parent, 0.0f, 0.5f, 0.01f, "KarmaDecay"); CreateLabeledSlider(parent, 0.0f, 0.5f, 0.01f, "KarmaDecay");
@@ -37,7 +41,7 @@ namespace Barotrauma
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "DamageFriendlyKarmaDecrease"); CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, "DamageFriendlyKarmaDecrease");
CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, "ReactorMeltdownKarmaDecrease"); CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, "ReactorMeltdownKarmaDecrease");
CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, "ReactorOverheatKarmaDecrease"); CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, "ReactorOverheatKarmaDecrease");
CreateLabeledSlider(parent, 0.0f, 20.0f, 1f, "AllowedWireDisconnectionsPerMinute"); CreateLabeledNumberInput(parent, 0, 20, "AllowedWireDisconnectionsPerMinute");
CreateLabeledSlider(parent, 0.0f, 20.0f, 0.5f, "WireDisconnectionKarmaDecrease"); CreateLabeledSlider(parent, 0.0f, 20.0f, 0.5f, "WireDisconnectionKarmaDecrease");
CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, "SpamFilterKarmaDecrease"); CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, "SpamFilterKarmaDecrease");
} }
@@ -74,5 +78,29 @@ namespace Barotrauma
GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, slider); GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, slider);
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
} }
private void CreateLabeledNumberInput(GUIComponent parent, int min, int max, string propertyName)
{
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f,
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
};
string labelText = TextManager.Get("Karma." + propertyName);
var label = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.8f), container.RectTransform),
labelText, font: GUI.SmallFont)
{
ToolTip = TextManager.Get("Karma." + propertyName + "ToolTip")
};
var numInput = new GUINumberInput(new RectTransform(new Vector2(0.3f, 0.8f), container.RectTransform), GUINumberInput.NumberType.Int)
{
MinValueInt = min,
MaxValueFloat = max
};
GameMain.NetworkMember.ServerSettings.AssignGUIComponent(propertyName, numInput);
}
} }
} }
@@ -1,5 +1,4 @@
using Lidgren.Network; using System;
using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace Barotrauma.Networking namespace Barotrauma.Networking
@@ -75,7 +74,7 @@ namespace Barotrauma.Networking
events.Add(newEvent); events.Add(newEvent);
} }
public void Write(NetOutgoingMessage msg, NetConnection serverConnection) public void Write(IWriteMessage msg, NetworkConnection serverConnection)
{ {
if (events.Count == 0 || serverConnection == null) return; if (events.Count == 0 || serverConnection == null) return;
@@ -97,7 +96,7 @@ namespace Barotrauma.Networking
//find the first event that hasn't been sent in roundtriptime or at all //find the first event that hasn't been sent in roundtriptime or at all
eventLastSent.TryGetValue(events[i].ID, out float lastSent); eventLastSent.TryGetValue(events[i].ID, out float lastSent);
if (lastSent > NetTime.Now - serverConnection.AverageRoundtripTime) if (lastSent > Lidgren.Network.NetTime.Now - 50) //TODO: reimplement serverConnection.AverageRoundtripTime
{ {
continue; continue;
} }
@@ -109,7 +108,7 @@ namespace Barotrauma.Networking
foreach (NetEntityEvent entityEvent in eventsToSync) foreach (NetEntityEvent entityEvent in eventsToSync)
{ {
eventLastSent[entityEvent.ID] = (float)NetTime.Now; eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
} }
msg.Write((byte)ClientNetObject.ENTITY_STATE); msg.Write((byte)ClientNetObject.ENTITY_STATE);
@@ -121,7 +120,7 @@ namespace Barotrauma.Networking
/// <summary> /// <summary>
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails. /// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
/// </summary> /// </summary>
public bool Read(ServerNetObject type, NetIncomingMessage msg, float sendingTime, List<IServerSerializable> entities) public bool Read(ServerNetObject type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
{ {
UInt16 unreceivedEntityEventCount = 0; UInt16 unreceivedEntityEventCount = 0;
@@ -176,7 +175,7 @@ namespace Barotrauma.Networking
IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable; IServerSerializable entity = Entity.FindEntityByID(entityID) as IServerSerializable;
entities.Add(entity); entities.Add(entity);
//skip the event if we've already received it or if the entity isn't found //skip the event if we've already received it or if the entity isn't found
if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null) if (thisEventID != (UInt16)(lastReceivedID + 1) || entity == null)
{ {
@@ -200,11 +199,11 @@ namespace Barotrauma.Networking
return false; return false;
} }
msg.Position += msgLength * 8; msg.BitPosition += msgLength * 8;
} }
else else
{ {
long msgPosition = msg.Position; long msgPosition = msg.BitPosition;
if (GameSettings.VerboseLogging) if (GameSettings.VerboseLogging)
{ {
DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")", DebugConsole.NewMessage("received msg " + thisEventID + " (" + entity.ToString() + ")",
@@ -231,7 +230,7 @@ namespace Barotrauma.Networking
} }
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
msg.Position = msgPosition + msgLength * 8; msg.BitPosition = (int)(msgPosition + msgLength * 8);
} }
} }
msg.ReadPadBits(); msg.ReadPadBits();
@@ -239,7 +238,7 @@ namespace Barotrauma.Networking
return true; return true;
} }
protected override void WriteEvent(NetBuffer buffer, NetEntityEvent entityEvent, Client recipient = null) protected override void WriteEvent(IWriteMessage buffer, NetEntityEvent entityEvent, Client recipient = null)
{ {
var clientEvent = entityEvent as ClientEntityEvent; var clientEvent = entityEvent as ClientEntityEvent;
if (clientEvent == null) return; if (clientEvent == null) return;
@@ -248,7 +247,7 @@ namespace Barotrauma.Networking
clientEvent.Sent = true; clientEvent.Sent = true;
} }
protected void ReadEvent(NetIncomingMessage buffer, IServerSerializable entity, float sendingTime) protected void ReadEvent(IReadMessage buffer, IServerSerializable entity, float sendingTime)
{ {
entity.ClientRead(ServerNetObject.ENTITY_EVENT, buffer, sendingTime); entity.ClientRead(ServerNetObject.ENTITY_EVENT, buffer, sendingTime);
} }
@@ -1,5 +1,4 @@
using Lidgren.Network; using System;
using System;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -15,7 +14,7 @@ namespace Barotrauma.Networking
serializable = entity; serializable = entity;
} }
public void Write(NetBuffer msg) public void Write(IWriteMessage msg)
{ {
msg.Write(CharacterStateID); msg.Write(CharacterStateID);
serializable.ClientWrite(msg, Data); serializable.ClientWrite(msg, Data);
@@ -5,7 +5,7 @@ namespace Barotrauma.Networking
{ {
partial class OrderChatMessage : ChatMessage partial class OrderChatMessage : ChatMessage
{ {
public override void ClientWrite(NetOutgoingMessage msg) public override void ClientWrite(IWriteMessage msg)
{ {
msg.Write((byte)ClientNetObject.CHAT_MESSAGE); msg.Write((byte)ClientNetObject.CHAT_MESSAGE);
msg.Write(NetStateID); msg.Write(NetStateID);
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Barotrauma.Networking
{
abstract class ClientPeer
{
public delegate void MessageCallback(IReadMessage message);
public delegate void DisconnectCallback(string msg);
public delegate void PasswordCallback(int salt, int retries);
public delegate void InitializationCompleteCallback();
public MessageCallback OnMessageReceived;
public DisconnectCallback OnDisconnect;
public PasswordCallback OnRequestPassword;
public InitializationCompleteCallback OnInitializationComplete;
public string Name;
public string Version { get; protected set; }
public NetworkConnection ServerConnection { get; protected set; }
public abstract void Start(object endPoint, int ownerKey);
public abstract void Close(string msg = null);
public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod);
public abstract void SendPassword(string password);
}
}
@@ -0,0 +1,253 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using Lidgren.Network;
using Facepunch.Steamworks;
using Barotrauma.Steam;
using System.Linq;
namespace Barotrauma.Networking
{
class LidgrenClientPeer : ClientPeer
{
private bool isActive;
private NetClient netClient;
private NetPeerConfiguration netPeerConfiguration;
private ConnectionInitialization initializationStep;
private int ownerKey;
private int passwordSalt;
private Auth.Ticket steamAuthTicket;
List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenClientPeer(string name)
{
ServerConnection = null;
Name = name;
netClient = null;
isActive = false;
}
public override void Start(object endPoint, int ownerKey)
{
if (isActive) { return; }
this.ownerKey = ownerKey;
netPeerConfiguration = new NetPeerConfiguration("barotrauma");
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt
| NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error);
netClient = new NetClient(netPeerConfiguration);
if (SteamManager.IsInitialized)
{
steamAuthTicket = SteamManager.GetAuthSessionTicket();
//TODO: wait for GetAuthSessionTicketResponse_t
if (steamAuthTicket == null)
{
throw new Exception("GetAuthSessionTicket returned null");
}
}
incomingLidgrenMessages = new List<NetIncomingMessage>();
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
if (!(endPoint is IPEndPoint ipEndPoint))
{
throw new InvalidCastException("endPoint is not IPEndPoint");
}
if (ServerConnection != null)
{
throw new InvalidOperationException("ServerConnection is not null");
}
netClient.Start();
ServerConnection = new LidgrenConnection("Server", netClient.Connect(ipEndPoint), 0);
ServerConnection.Status = NetworkConnectionStatus.Connected;
isActive = true;
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
netClient.ReadMessages(incomingLidgrenMessages);
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (inc.SenderConnection != (ServerConnection as LidgrenConnection).NetConnection) { continue; }
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
incomingLidgrenMessages.Clear();
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (!isActive) { return; }
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
//DebugConsole.NewMessage(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte);
if (isConnectionInitializationStep && initializationStep != ConnectionInitialization.Success)
{
ReadConnectionInitializationStep(inc);
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
OnInitializationComplete?.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, ServerConnection);
OnMessageReceived?.Invoke(msg);
}
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (!isActive) { return; }
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
switch (status)
{
case NetConnectionStatus.Disconnected:
string disconnectMsg = inc.ReadString();
Close(disconnectMsg);
break;
}
}
private void ReadConnectionInitializationStep(NetIncomingMessage inc)
{
if (!isActive) { return; }
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(step + " " + initializationStep);
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
NetOutgoingMessage outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(Name);
outMsg.Write(ownerKey);
outMsg.Write(SteamManager.GetSteamID());
if (steamAuthTicket == null)
{
outMsg.Write((UInt16)0);
}
else
{
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
}
outMsg.Write(GameMain.Version.ToString());
IEnumerable<ContentPackage> mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
outMsg.WriteVariableInt32(mpContentPackages.Count());
foreach (ContentPackage contentPackage in mpContentPackages)
{
outMsg.Write(contentPackage.Name);
outMsg.Write(contentPackage.MD5hash.Hash);
}
netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
break;
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
public override void SendPassword(string password)
{
if (!isActive) { return; }
if (initializationStep != ConnectionInitialization.Password) { return; }
NetOutgoingMessage outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.Password);
byte[] saltedPw = ServerSettings.SaltPassword(NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(password)), passwordSalt);
outMsg.Write((byte)saltedPw.Length);
outMsg.Write(saltedPw, 0, saltedPw.Length);
netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
}
public override void Close(string msg = null)
{
if (!isActive) { return; }
isActive = false;
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting"));
netClient = null;
steamAuthTicket?.Cancel(); steamAuthTicket = null;
OnDisconnect?.Invoke(msg);
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
if (!isActive) { return; }
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
}
}
}
@@ -0,0 +1,338 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Barotrauma.Steam;
using Facepunch.Steamworks;
using System.Threading;
namespace Barotrauma.Networking
{
class SteamP2PClientPeer : ClientPeer
{
private bool isActive;
private UInt64 hostSteamId;
private ConnectionInitialization initializationStep;
private int passwordSalt;
private Auth.Ticket steamAuthTicket;
private double timeout;
private double heartbeatTimer;
private List<IReadMessage> incomingInitializationMessages;
private List<IReadMessage> incomingDataMessages;
public SteamP2PClientPeer(string name)
{
ServerConnection = null;
Name = name;
isActive = false;
}
public override void Start(object endPoint, int ownerKey)
{
steamAuthTicket = SteamManager.GetAuthSessionTicket();
//TODO: wait for GetAuthSessionTicketResponse_t
if (steamAuthTicket == null)
{
throw new Exception("GetAuthSessionTicket returned null");
}
if (!(endPoint is UInt64 steamIdEndpoint))
{
throw new InvalidCastException("endPoint is not UInt64");
}
hostSteamId = steamIdEndpoint;
Steam.SteamManager.Instance.Networking.OnIncomingConnection = OnIncomingConnection;
Steam.SteamManager.Instance.Networking.OnP2PData = OnP2PData;
Steam.SteamManager.Instance.Networking.SetListenChannel(0, true);
ServerConnection = new SteamP2PConnection("Server", hostSteamId);
incomingInitializationMessages = new List<IReadMessage>();
incomingDataMessages = new List<IReadMessage>();
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ConnectionStarted);
SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes,
Facepunch.Steamworks.Networking.SendType.Reliable);
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
timeout = 20.0;
heartbeatTimer = 1.0;
isActive = true;
}
private bool OnIncomingConnection(UInt64 steamId)
{
if (!isActive) { return false; }
return steamId == hostSteamId;
}
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int channel)
{
if (!isActive) { return; }
if (steamId != hostSteamId) { return; }
timeout = 20.0;
byte incByte = data[0];
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!isServerMessage) { return; }
if (isConnectionInitializationStep)
{
ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8);
ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8+32);
ulong lobbyId = low + (high << 32);
Steam.SteamManager.JoinLobby(lobbyId, false);
IReadMessage inc = new ReadOnlyMessage(data, false, 1+8, dataLength - 9, ServerConnection);
if (initializationStep != ConnectionInitialization.Success)
{
incomingInitializationMessages.Add(inc);
}
}
else if (isHeartbeatMessage)
{
return; //TODO: implement heartbeats
}
else if (isDisconnectMessage)
{
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
string msg = inc.ReadString();
Close(msg);
}
else
{
UInt16 length = data[1];
length |= (UInt16)(((UInt32)data[2]) << 8);
IReadMessage inc = new ReadOnlyMessage(data, isCompressed, 3, length, ServerConnection);
incomingDataMessages.Add(inc);
}
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
timeout -= deltaTime;
heartbeatTimer -= deltaTime;
if (heartbeatTimer < 0.0)
{
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Unreliable);
outMsg.Write((byte)PacketHeader.IsHeartbeatMessage);
SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes,
Facepunch.Steamworks.Networking.SendType.Unreliable);
heartbeatTimer = 5.0;
}
if (timeout < 0.0)
{
Close("Timed out");
return;
}
if (initializationStep != ConnectionInitialization.Success)
{
if (incomingDataMessages.Count > 0)
{
OnInitializationComplete?.Invoke();
initializationStep = ConnectionInitialization.Success;
}
else
{
foreach (IReadMessage inc in incomingInitializationMessages)
{
ReadConnectionInitializationStep(inc);
}
}
}
if (initializationStep == ConnectionInitialization.Success)
{
foreach (IReadMessage inc in incomingDataMessages)
{
OnMessageReceived?.Invoke(inc);
}
}
incomingInitializationMessages.Clear();
incomingDataMessages.Clear();
}
private void ReadConnectionInitializationStep(IReadMessage inc)
{
if (!isActive) { return; }
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
//DebugConsole.NewMessage(step + " " + initializationStep);
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(Name);
outMsg.Write(SteamManager.GetSteamID());
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
outMsg.Write(GameMain.Version.ToString());
IEnumerable<ContentPackage> mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count());
foreach (ContentPackage contentPackage in mpContentPackages)
{
outMsg.Write(contentPackage.Name);
outMsg.Write(contentPackage.MD5hash.Hash);
}
heartbeatTimer = 5.0;
SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes,
Facepunch.Steamworks.Networking.SendType.Reliable);
break;
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
if (!isActive) { return; }
byte[] buf = new byte[msg.LengthBytes + 4];
buf[0] = (byte)deliveryMethod;
byte[] bufAux = new byte[msg.LengthBytes];
bool isCompressed; int length;
msg.PrepareForSending(ref bufAux, out isCompressed, out length);
buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None);
buf[2] = (byte)(length & 0xff);
buf[3] = (byte)((length >> 8) & 0xff);
Array.Copy(bufAux, 0, buf, 4, length);
Facepunch.Steamworks.Networking.SendType sendType;
switch (deliveryMethod)
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Facepunch.Steamworks.Networking.SendType.Reliable;
break;
default:
sendType = Facepunch.Steamworks.Networking.SendType.Unreliable;
break;
}
if (length + 8 >= MsgConstants.MTU)
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Facepunch.Steamworks.Networking.SendType.Reliable;
}
heartbeatTimer = 5.0;
bool successSend = SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, buf, length + 4, sendType);
if (!successSend)
{
if (sendType != Facepunch.Steamworks.Networking.SendType.Reliable)
{
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + length.ToString() + " bytes)");
sendType = Facepunch.Steamworks.Networking.SendType.Reliable;
successSend = Steam.SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, buf, length + 4, sendType);
}
if (!successSend)
{
DebugConsole.ThrowError("Failed to send message to remote peer! (" + length.ToString() + " bytes)");
}
}
}
public override void SendPassword(string password)
{
if (!isActive) { return; }
if (initializationStep != ConnectionInitialization.Password) { return; }
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.Password);
byte[] saltedPw = ServerSettings.SaltPassword(Lidgren.Network.NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(password)), passwordSalt);
outMsg.Write((byte)saltedPw.Length);
outMsg.Write(saltedPw, 0, saltedPw.Length);
heartbeatTimer = 5.0;
SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes,
Facepunch.Steamworks.Networking.SendType.Reliable);
}
public override void Close(string msg = null)
{
if (!isActive) { return; }
SteamManager.LeaveLobby();
isActive = false;
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
outMsg.Write(msg ?? "Disconnected");
SteamManager.Instance.Networking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes,
Facepunch.Steamworks.Networking.SendType.Reliable);
Thread.Sleep(100);
Steam.SteamManager.Instance.Networking.OnIncomingConnection = null;
Steam.SteamManager.Instance.Networking.OnP2PData = null;
Steam.SteamManager.Instance.Networking.SetListenChannel(0, false);
Steam.SteamManager.Instance.Networking.CloseSession(hostSteamId);
steamAuthTicket?.Cancel(); steamAuthTicket = null;
hostSteamId = 0;
OnDisconnect?.Invoke(msg);
}
}
}
@@ -0,0 +1,472 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using Lidgren.Network;
using Facepunch.Steamworks;
using Barotrauma.Steam;
using System.Linq;
using System.Threading;
namespace Barotrauma.Networking
{
class SteamP2POwnerPeer : ClientPeer
{
private bool isActive;
private NetClient netClient;
private NetPeerConfiguration netPeerConfiguration;
private ConnectionInitialization initializationStep;
private UInt64 selfSteamID;
List<NetIncomingMessage> incomingLidgrenMessages;
class RemotePeer
{
public UInt64 SteamID;
public double? DisconnectTime;
public bool Authenticating;
public bool Authenticated;
public List<Pair<NetDeliveryMethod, NetOutgoingMessage>> UnauthedMessages;
public RemotePeer(UInt64 steamId)
{
SteamID = steamId;
DisconnectTime = null;
Authenticating = false;
Authenticated = false;
UnauthedMessages = new List<Pair<NetDeliveryMethod, NetOutgoingMessage>>();
}
}
List<RemotePeer> remotePeers;
public SteamP2POwnerPeer(string name)
{
ServerConnection = null;
Name = name;
netClient = null;
isActive = false;
selfSteamID = Steam.SteamManager.GetSteamID();
}
public override void Start(object endPoint, int ownerKey)
{
if (isActive) { return; }
netPeerConfiguration = new NetPeerConfiguration("barotrauma");
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt
| NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error);
netClient = new NetClient(netPeerConfiguration);
incomingLidgrenMessages = new List<NetIncomingMessage>();
initializationStep = ConnectionInitialization.SteamTicketAndVersion;
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Loopback, Steam.SteamManager.STEAMP2P_OWNER_PORT);
netClient.Start();
ServerConnection = new LidgrenConnection("Server", netClient.Connect(ipEndPoint), 0);
ServerConnection.Status = NetworkConnectionStatus.Connected;
remotePeers = new List<RemotePeer>();
Steam.SteamManager.Instance.Networking.OnIncomingConnection = OnIncomingConnection;
Steam.SteamManager.Instance.Networking.OnP2PData = OnP2PData;
Steam.SteamManager.Instance.Networking.SetListenChannel(0, true);
Steam.SteamManager.Instance.Auth.OnAuthChange = OnAuthChange;
isActive = true;
}
private void OnAuthChange(ulong steamID, ulong ownerID, ClientAuthStatus status)
{
RemotePeer remotePeer = remotePeers.Find(p => p.SteamID == steamID);
DebugConsole.NewMessage(steamID + " validation: " + status + ", " + (remotePeer != null));
if (remotePeer == null) { return; }
if (remotePeer.Authenticated)
{
if (status != ClientAuthStatus.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication status changed: " + status.ToString());
}
return;
}
if (status == ClientAuthStatus.OK)
{
remotePeer.Authenticated = true;
remotePeer.Authenticating = false;
foreach (var msg in remotePeer.UnauthedMessages)
{
netClient.SendMessage(msg.Second, msg.First);
}
remotePeer.UnauthedMessages.Clear();
}
else
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam authentication failed: " + status.ToString());
return;
}
}
private bool OnIncomingConnection(UInt64 steamId)
{
if (!isActive) { return false; }
if (!remotePeers.Any(p => p.SteamID == steamId))
{
remotePeers.Add(new RemotePeer(steamId));
}
return true; //accept all connections, the server will figure things out later
}
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int channel)
{
if (!isActive) { return; }
RemotePeer remotePeer = remotePeers.Find(p => p.SteamID == steamId);
if (remotePeer == null || remotePeer.DisconnectTime != null)
{
return;
}
NetOutgoingMessage outMsg = netClient.CreateMessage();
outMsg.Write(steamId);
outMsg.Write(data, 1, dataLength - 1);
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch ((DeliveryMethod)data[0])
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
byte incByte = data[1];
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!remotePeer.Authenticated)
{
if (!remotePeer.Authenticating)
{
if (isConnectionInitializationStep)
{
remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{
remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name
authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength);
ClientStartAuthSessionResult authSessionStartState = Steam.SteamManager.StartAuthSession(ticket, steamId);
if (authSessionStartState != ClientStartAuthSessionResult.OK)
{
DisconnectPeer(remotePeer, DisconnectReason.SteamAuthenticationFailed.ToString() + "/ Steam auth session failed to start: " + authSessionStartState.ToString());
return;
}
}
}
}
}
if (remotePeer.Authenticating)
{
remotePeer.UnauthedMessages.Add(new Pair<NetDeliveryMethod, NetOutgoingMessage>(lidgrenDeliveryMethod, outMsg));
}
else
{
netClient.SendMessage(outMsg, lidgrenDeliveryMethod);
}
}
public override void Update(float deltaTime)
{
if (!isActive) { return; }
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
if (remotePeers[i].DisconnectTime != null && remotePeers[i].DisconnectTime < Timing.TotalTime)
{
ClosePeerSession(remotePeers[i]);
}
}
netClient.ReadMessages(incomingLidgrenMessages);
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
{
if (inc.SenderConnection != (ServerConnection as LidgrenConnection).NetConnection) { continue; }
switch (inc.MessageType)
{
case NetIncomingMessageType.Data:
HandleDataMessage(inc);
break;
case NetIncomingMessageType.StatusChanged:
HandleStatusChanged(inc);
break;
}
}
incomingLidgrenMessages.Clear();
}
private void HandleDataMessage(NetIncomingMessage inc)
{
if (!isActive) { return; }
UInt64 recipientSteamId = inc.ReadUInt64();
int p2pDataStart = inc.PositionInBytes;
byte incByte = inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (recipientSteamId != selfSteamID)
{
if (!isServerMessage)
{
DebugConsole.ThrowError("Received non-server message meant for remote peer");
return;
}
RemotePeer peer = remotePeers.Find(p => p.SteamID == recipientSteamId);
if (peer == null) { return; }
if (isDisconnectMessage)
{
DisconnectPeer(peer, inc.ReadString());
return;
}
Facepunch.Steamworks.Networking.SendType sendType;
switch (inc.DeliveryMethod)
{
case NetDeliveryMethod.ReliableUnordered:
case NetDeliveryMethod.ReliableSequenced:
case NetDeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Facepunch.Steamworks.Networking.SendType.Reliable;
break;
default:
sendType = Facepunch.Steamworks.Networking.SendType.Unreliable;
break;
}
byte[] p2pData;
if (isConnectionInitializationStep)
{
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
p2pData[0] = inc.Data[p2pDataStart];
Lidgren.Network.NetBitWriter.WriteUInt64(Steam.SteamManager.Instance.Lobby.CurrentLobby, 64, p2pData, 8);
Array.Copy(inc.Data, p2pDataStart+1, p2pData, 9, inc.LengthBytes - p2pDataStart - 1);
}
else
{
p2pData = new byte[inc.LengthBytes - p2pDataStart];
Array.Copy(inc.Data, p2pDataStart, p2pData, 0, p2pData.Length);
}
if (p2pData.Length + 4 >= MsgConstants.MTU)
{
DebugConsole.Log("WARNING: message length comes close to exceeding MTU, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
sendType = Facepunch.Steamworks.Networking.SendType.Reliable;
}
bool successSend = Steam.SteamManager.Instance.Networking.SendP2PPacket(recipientSteamId, p2pData, p2pData.Length, sendType);
if (!successSend)
{
if (sendType != Facepunch.Steamworks.Networking.SendType.Reliable)
{
DebugConsole.Log("WARNING: message couldn't be sent unreliably, forcing reliable send (" + p2pData.Length.ToString() + " bytes)");
sendType = Facepunch.Steamworks.Networking.SendType.Reliable;
successSend = Steam.SteamManager.Instance.Networking.SendP2PPacket(recipientSteamId, p2pData, p2pData.Length, sendType);
}
if (!successSend)
{
DebugConsole.ThrowError("Failed to send message to remote peer! (" + p2pData.Length.ToString() + " bytes)");
}
}
}
else
{
if (isDisconnectMessage)
{
DebugConsole.ThrowError("Received disconnect message from owned server");
return;
}
if (!isServerMessage)
{
DebugConsole.ThrowError("Received non-server message from owned server");
return;
}
if (isHeartbeatMessage)
{
return; //timeout is handled by Lidgren, ignore this message
}
if (isConnectionInitializationStep)
{
NetOutgoingMessage outMsg = netClient.CreateMessage();
outMsg.Write(selfSteamID);
outMsg.Write((byte)(PacketHeader.IsConnectionInitializationStep));
outMsg.Write(Name);
netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
return;
}
else
{
if (initializationStep != ConnectionInitialization.Success)
{
OnInitializationComplete?.Invoke();
initializationStep = ConnectionInitialization.Success;
}
UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, ServerConnection);
OnMessageReceived?.Invoke(msg);
return;
}
}
}
private void DisconnectPeer(RemotePeer peer, string msg)
{
if (!string.IsNullOrWhiteSpace(msg))
{
if (peer.DisconnectTime == null)
{
peer.DisconnectTime = Timing.TotalTime + 1.0;
}
IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)(PacketHeader.IsServerMessage | PacketHeader.IsDisconnectMessage));
outMsg.Write(msg);
Steam.SteamManager.Instance.Networking.SendP2PPacket(peer.SteamID, outMsg.Buffer, outMsg.LengthBytes,
Facepunch.Steamworks.Networking.SendType.Reliable);
}
else
{
ClosePeerSession(peer);
}
}
private void ClosePeerSession(RemotePeer peer)
{
Steam.SteamManager.Instance.Networking.CloseSession(peer.SteamID);
remotePeers.Remove(peer);
}
private void HandleStatusChanged(NetIncomingMessage inc)
{
if (!isActive) { return; }
NetConnectionStatus status = (NetConnectionStatus)inc.ReadByte();
switch (status)
{
case NetConnectionStatus.Disconnected:
string disconnectMsg = inc.ReadString();
Close(disconnectMsg);
break;
}
}
public override void SendPassword(string password)
{
return; //owner doesn't send passwords
}
public override void Close(string msg = null)
{
if (!isActive) { return; }
isActive = false;
for (int i=remotePeers.Count-1;i>=0;i--)
{
DisconnectPeer(remotePeers[i], msg ?? DisconnectReason.ServerShutdown.ToString());
}
Thread.Sleep(100);
for (int i = remotePeers.Count - 1; i >= 0; i--)
{
ClosePeerSession(remotePeers[i]);
}
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting"));
netClient = null;
OnDisconnect?.Invoke(msg);
Steam.SteamManager.Instance.Networking.OnIncomingConnection = null;
Steam.SteamManager.Instance.Networking.OnP2PData = null;
Steam.SteamManager.Instance.Networking.SetListenChannel(0, false);
Steam.SteamManager.Instance.Auth.OnAuthChange = null;
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{
if (!isActive) { return; }
NetDeliveryMethod lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
switch (deliveryMethod)
{
case DeliveryMethod.Unreliable:
lidgrenDeliveryMethod = NetDeliveryMethod.Unreliable;
break;
case DeliveryMethod.Reliable:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableUnordered;
break;
case DeliveryMethod.ReliableOrdered:
lidgrenDeliveryMethod = NetDeliveryMethod.ReliableOrdered;
break;
}
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
byte[] msgData = new byte[msg.LengthBytes];
msg.PrepareForSending(ref msgData, out bool isCompressed, out int length);
lidgrenMsg.Write(selfSteamID);
lidgrenMsg.Write((byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None));
lidgrenMsg.Write((UInt16)length);
lidgrenMsg.Write(msgData, 0, length);
netClient.SendMessage(lidgrenMsg, lidgrenDeliveryMethod);
}
}
}
@@ -21,8 +21,7 @@ namespace Barotrauma.Networking
GameMain.Client.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server); GameMain.Client.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server);
} }
} }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
public void ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime)
{ {
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length); var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
@@ -1,5 +1,6 @@
using Barotrauma.Steam; using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -9,6 +10,9 @@ namespace Barotrauma.Networking
{ {
public string IP; public string IP;
public string Port; public string Port;
public UInt64 LobbyID;
public string ServerName; public string ServerName;
public string ServerMessage; public string ServerMessage;
public bool GameStarted; public bool GameStarted;
@@ -5,7 +5,7 @@ using System.Linq;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
partial class ServerLog public partial class ServerLog
{ {
public GUIButton LogFrame; public GUIButton LogFrame;
private GUIListBox listBox; private GUIListBox listBox;
@@ -1,5 +1,4 @@
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.ComponentModel; using System.ComponentModel;
@@ -31,6 +30,10 @@ namespace Barotrauma.Networking
else if (GUIComponent is GUIScrollBar scrollBar) return scrollBar.BarScrollValue; else if (GUIComponent is GUIScrollBar scrollBar) return scrollBar.BarScrollValue;
else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected; else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected;
else if (GUIComponent is GUIDropDown dropdown) return dropdown.SelectedData; else if (GUIComponent is GUIDropDown dropdown) return dropdown.SelectedData;
else if (GUIComponent is GUINumberInput numInput)
{
if (numInput.InputType == GUINumberInput.NumberType.Int) { return numInput.IntValue; } else { return numInput.FloatValue; }
}
return null; return null;
} }
set set
@@ -38,9 +41,30 @@ namespace Barotrauma.Networking
if (GUIComponent == null) return; if (GUIComponent == null) return;
else if (GUIComponent is GUITickBox tickBox) tickBox.Selected = (bool)value; else if (GUIComponent is GUITickBox tickBox) tickBox.Selected = (bool)value;
else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value; else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value;
else if (GUIComponent is GUIScrollBar scrollBar) scrollBar.BarScrollValue = (float)value; else if (GUIComponent is GUIScrollBar scrollBar)
{
if (value.GetType() == typeof(int))
{
scrollBar.BarScrollValue = (int)value;
}
else
{
scrollBar.BarScrollValue = (float)value;
}
}
else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) radioButtonGroup.Selected = (Enum)value; else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) radioButtonGroup.Selected = (Enum)value;
else if (GUIComponent is GUIDropDown dropdown) dropdown.SelectItem(value); else if (GUIComponent is GUIDropDown dropdown) dropdown.SelectItem(value);
else if (GUIComponent is GUINumberInput numInput)
{
if (numInput.InputType == GUINumberInput.NumberType.Int)
{
numInput.IntValue = (int)value;
}
else
{
numInput.FloatValue = (float)value;
}
}
} }
} }
@@ -68,7 +92,7 @@ namespace Barotrauma.Networking
} }
} }
public void ClientAdminRead(NetBuffer incMsg) public void ClientAdminRead(IReadMessage incMsg)
{ {
int count = incMsg.ReadUInt16(); int count = incMsg.ReadUInt16();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
@@ -91,7 +115,7 @@ namespace Barotrauma.Networking
else else
{ {
UInt32 size = incMsg.ReadVariableUInt32(); UInt32 size = incMsg.ReadVariableUInt32();
incMsg.Position += 8 * size; incMsg.BitPosition += (int)(8 * size);
} }
} }
@@ -100,10 +124,14 @@ namespace Barotrauma.Networking
Whitelist.ClientAdminRead(incMsg); Whitelist.ClientAdminRead(incMsg);
} }
public void ClientRead(NetBuffer incMsg) public void ClientRead(IReadMessage incMsg)
{ {
ServerName = incMsg.ReadString(); ServerName = incMsg.ReadString();
ServerMessageText = incMsg.ReadString(); ServerMessageText = incMsg.ReadString();
MaxPlayers = incMsg.ReadByte();
HasPassword = incMsg.ReadBoolean();
isPublic = incMsg.ReadBoolean();
incMsg.ReadPadBits();
TickRate = incMsg.ReadRangedInteger(1, 60); TickRate = incMsg.ReadRangedInteger(1, 60);
GameMain.NetworkMember.TickRate = TickRate; GameMain.NetworkMember.TickRate = TickRate;
@@ -123,7 +151,7 @@ namespace Barotrauma.Networking
{ {
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return; if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
NetOutgoingMessage outMsg = GameMain.NetworkMember.NetPeer.CreateMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS); outMsg.Write((byte)ClientPacketHeader.SERVER_SETTINGS);
@@ -191,7 +219,7 @@ namespace Barotrauma.Networking
outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text); outMsg.Write(GameMain.NetLobbyScreen.SeedBox.Text);
} }
(GameMain.NetworkMember.NetPeer as NetClient).SendMessage(outMsg, NetDeliveryMethod.ReliableOrdered); GameMain.Client.ClientPeer.Send(outMsg, DeliveryMethod.Reliable);
} }
//GUI stuff //GUI stuff
@@ -508,7 +536,7 @@ namespace Barotrauma.Networking
var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton"));
GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox); GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox);
var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio")); /*var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio"));
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel);
var traitorRatioSlider = slider; var traitorRatioSlider = slider;
@@ -553,7 +581,7 @@ namespace Barotrauma.Networking
GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider); GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider);
traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll); traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll);
traitorRatioBox.OnSelected(traitorRatioBox); traitorRatioBox.OnSelected(traitorRatioBox);*/
var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true) var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true)
{ {
@@ -829,4 +857,4 @@ namespace Barotrauma.Networking
return false; return false;
} }
} }
} }
@@ -6,70 +6,269 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace Barotrauma.Steam namespace Barotrauma.Steam
{ {
partial class SteamManager partial class SteamManager
{ {
private static List<string> initializationErrors = new List<string>(); public Facepunch.Steamworks.Networking Networking => client?.Networking;
public static IEnumerable<string> InitializationErrors public Facepunch.Steamworks.User User => client?.User;
{ public Facepunch.Steamworks.Friends Friends => client?.Friends;
get { return initializationErrors; } public Facepunch.Steamworks.Overlay Overlay => client?.Overlay;
} public Facepunch.Steamworks.Auth Auth => client?.Auth;
public Facepunch.Steamworks.Lobby Lobby => client?.Lobby;
public Facepunch.Steamworks.LobbyList LobbyList => client?.LobbyList;
private SteamManager() private SteamManager()
{ {
client = null;
isInitialized = InitializeClient();
}
private bool InitializeClient()
{
if (client != null) { return true; }
bool clientInitialized = false;
try try
{ {
client = new Facepunch.Steamworks.Client(AppID); client = new Facepunch.Steamworks.Client(AppID);
isInitialized = client.IsSubscribed && client.IsValid; clientInitialized = client.IsSubscribed && client.IsValid;
if (isInitialized) if (clientInitialized)
{ {
DebugConsole.Log("Logged in as " + client.Username + " (SteamID " + client.SteamId + ")"); DebugConsole.NewMessage("Logged in as " + client.Username + " (SteamID " + SteamIDUInt64ToString(client.SteamId) + ")");
} }
} }
catch (DllNotFoundException) catch (DllNotFoundException)
{ {
isInitialized = false; clientInitialized = false;
initializationErrors.Add("SteamDllNotFound"); initializationErrors.Add("SteamDllNotFound");
} }
catch (Exception) catch (Exception)
{ {
isInitialized = false; clientInitialized = false;
initializationErrors.Add("SteamClientInitFailed"); initializationErrors.Add("SteamClientInitFailed");
} }
if (!isInitialized) if (!clientInitialized)
{ {
try try
{ {
Facepunch.Steamworks.Client.Instance.Dispose(); Facepunch.Steamworks.Client.Instance.Dispose();
} }
catch (Exception e) catch (Exception e)
{ {
if (GameSettings.VerboseLogging) DebugConsole.ThrowError("Disposing Steam client failed.", e); if (GameSettings.VerboseLogging) DebugConsole.ThrowError("Disposing Steam client failed.", e);
} }
client = null;
}
return clientInitialized;
}
private enum LobbyState
{
NotConnected,
Creating,
Owner,
Joining,
Joined
}
private static UInt64 lobbyID = 0;
private static LobbyState lobbyState = LobbyState.NotConnected;
private static string lobbyIP = "";
private static Thread lobbyIPRetrievalThread;
private static void RetrieveLobbyIP()
{
//TODO: set up our own server for IP retrieval?
Server tempServer = null;
try
{
var serverInit = new ServerInit("Barotrauma", "Barotrauma IP Retrieval")
{
GamePort = (ushort)27015,
QueryPort = (ushort)27016
};
tempServer = new Server(AppID, serverInit, false);
if (!tempServer.IsValid)
{
tempServer.Dispose();
tempServer = null;
DebugConsole.ThrowError("Failed to retrieve public IP: Initializing Steam server failed.");
return;
}
tempServer.LogOnAnonymous();
lobbyIP = "";
string error = "Timed out.";
for (int i = 0; i < 30*60; i++)
{
if (instance.client.Lobby.CurrentLobby == 0)
{
error = "";
break;
}
tempServer.Update();
tempServer.ForceHeartbeat();
if (tempServer.PublicIp != null)
{
if (instance.client.Lobby.CurrentLobby != 0)
{
lobbyIP = tempServer.PublicIp.ToString();
DebugConsole.NewMessage("Successfully retrieved public IP: " + lobbyIP, Microsoft.Xna.Framework.Color.Lime);
instance.client.Lobby.CurrentLobbyData.SetData("hostipaddress", lobbyIP);
}
else
{
error = "";
lobbyIP = "";
}
break;
}
Thread.Sleep(16);
}
tempServer.Dispose();
tempServer = null;
if (string.IsNullOrWhiteSpace(lobbyIP) && !string.IsNullOrWhiteSpace(error))
{
DebugConsole.ThrowError("Failed to retrieve public IP: "+error);
}
}
catch
{
tempServer?.Dispose();
tempServer = null;
} }
} }
public static ulong GetSteamID() public static void CreateLobby(ServerSettings serverSettings)
{ {
if (instance == null || !instance.isInitialized) instance.client.Lobby.OnLobbyJoined = null;
instance.client.Lobby.OnLobbyCreated = (success) =>
{ {
return 0; if (!success)
{
DebugConsole.ThrowError("Failed to create Steam lobby!");
lobbyState = LobbyState.NotConnected;
return;
}
DebugConsole.NewMessage("Lobby created!", Microsoft.Xna.Framework.Color.Lime);
lobbyIPRetrievalThread?.Abort();
lobbyIPRetrievalThread?.Join();
lobbyIPRetrievalThread = null;
lobbyIPRetrievalThread = new Thread(new ThreadStart(RetrieveLobbyIP));
lobbyIPRetrievalThread.IsBackground = true;
lobbyIPRetrievalThread.Start();
lobbyState = LobbyState.Owner;
lobbyID = instance.client.Lobby.CurrentLobby;
UpdateLobby(serverSettings);
};
if (lobbyState != LobbyState.NotConnected) { return; }
lobbyState = LobbyState.Creating;
instance.client.Lobby.Create(serverSettings.isPublic ? Lobby.Type.Public : Lobby.Type.FriendsOnly, serverSettings.MaxPlayers+10);
instance.client.Lobby.Joinable = true;
}
public static void UpdateLobby(ServerSettings serverSettings)
{
if (GameMain.Client == null)
{
LeaveLobby();
} }
return instance.client.SteamId;
if (lobbyState == LobbyState.NotConnected)
{
CreateLobby(serverSettings);
}
if (lobbyState != LobbyState.Owner)
{
return;
}
var contentPackages = GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
instance.client.Lobby.Name = serverSettings.ServerName;
instance.client.Lobby.Owner = Steam.SteamManager.GetSteamID();
instance.client.Lobby.MaxMembers = serverSettings.MaxPlayers+10;
instance.client.Lobby.CurrentLobbyData.SetData("playercount", (GameMain.Client?.ConnectedClients?.Count??0).ToString());
instance.client.Lobby.CurrentLobbyData.SetData("maxplayernum", serverSettings.MaxPlayers.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("hostipaddress", lobbyIP);
//instance.client.Lobby.CurrentLobbyData.SetData("connectsteamid", Steam.SteamManager.GetSteamID().ToString());
instance.client.Lobby.CurrentLobbyData.SetData("haspassword", serverSettings.HasPassword.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("message", serverSettings.ServerMessageText);
instance.client.Lobby.CurrentLobbyData.SetData("version", GameMain.Version.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
instance.client.Lobby.CurrentLobbyData.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.MD5hash.Hash)));
instance.client.Lobby.CurrentLobbyData.SetData("contentpackageurl", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopUrl ?? "")));
instance.client.Lobby.CurrentLobbyData.SetData("usingwhitelist", (serverSettings.Whitelist != null && serverSettings.Whitelist.Enabled).ToString());
instance.client.Lobby.CurrentLobbyData.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("voicechatenabled", serverSettings.VoiceChatEnabled.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("allowspectating", serverSettings.AllowSpectating.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("allowrespawn", serverSettings.AllowRespawn.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("traitors", serverSettings.TraitorsEnabled.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("gamestarted", GameMain.Client.GameStarted.ToString());
instance.client.Lobby.CurrentLobbyData.SetData("gamemode", serverSettings.GameModeIdentifier);
DebugConsole.NewMessage("Lobby updated!", Microsoft.Xna.Framework.Color.Lime);
} }
public static string GetUsername() public static void LeaveLobby()
{ {
if (instance == null || !instance.isInitialized) if (lobbyState != LobbyState.NotConnected)
{ {
return ""; lobbyIPRetrievalThread?.Abort();
lobbyIPRetrievalThread?.Join();
lobbyIPRetrievalThread = null;
instance.client.Lobby.Leave();
lobbyID = 0;
lobbyIP = "";
lobbyState = LobbyState.NotConnected;
instance.client.Lobby.OnLobbyJoined = null;
} }
return instance.client.Username; }
public static void JoinLobby(UInt64 id, bool joinServer)
{
if (instance.client.Lobby.CurrentLobby == id) { return; }
if (lobbyID == id) { return; }
instance.client.Lobby.OnLobbyJoined = (success) =>
{
try
{
if (!success)
{
DebugConsole.ThrowError("Failed to join Steam lobby: "+id.ToString());
return;
}
lobbyState = LobbyState.Joined;
lobbyID = instance.client.Lobby.CurrentLobby;
if (joinServer)
{
GameMain.Instance.ConnectLobby = 0;
GameMain.Instance.ConnectName = instance.client.Lobby.Name;
GameMain.Instance.ConnectEndpoint = instance.client.Lobby.Owner.ToString();
}
}
finally
{
instance.client.Lobby.OnLobbyJoined = null;
}
};
lobbyState = LobbyState.Joining;
lobbyID = id;
instance.client.Lobby.Join(id);
} }
public static ulong GetWorkshopItemIDFromUrl(string url) public static ulong GetWorkshopItemIDFromUrl(string url)
@@ -112,13 +311,16 @@ namespace Barotrauma.Steam
//the response is queried using the server's query port, not the game port, //the response is queried using the server's query port, not the game port,
//so it may be possible to play on the server even if it doesn't respond to server list queries //so it may be possible to play on the server even if it doesn't respond to server list queries
var query = instance.client.ServerList.Internet(filter); var query = instance.client.ServerList.Internet(filter);
query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; query.OnUpdate = () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); };
query.OnFinished = onFinished; query.OnFinished = onFinished;
var localQuery = instance.client.ServerList.Local(filter); var localQuery = instance.client.ServerList.Local(filter);
localQuery.OnUpdate += () => { UpdateServerQuery(localQuery, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; localQuery.OnUpdate = () => { UpdateServerQuery(localQuery, onServerFound, onServerRulesReceived, includeUnresponsive: true); };
localQuery.OnFinished = onFinished; localQuery.OnFinished = onFinished;
instance.client.LobbyList.OnLobbiesUpdated = () => { UpdateLobbyQuery(onServerFound, onServerRulesReceived, onFinished); };
instance.client.LobbyList.Refresh();
return true; return true;
} }
@@ -141,7 +343,7 @@ namespace Barotrauma.Steam
//the response is queried using the server's query port, not the game port, //the response is queried using the server's query port, not the game port,
//so it may be possible to play on the server even if it doesn't respond to server list queries //so it may be possible to play on the server even if it doesn't respond to server list queries
var query = instance.client.ServerList.Favourites(filter); var query = instance.client.ServerList.Favourites(filter);
query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; query.OnUpdate = () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); };
query.OnFinished = onFinished; query.OnFinished = onFinished;
return true; return true;
@@ -166,12 +368,71 @@ namespace Barotrauma.Steam
//the response is queried using the server's query port, not the game port, //the response is queried using the server's query port, not the game port,
//so it may be possible to play on the server even if it doesn't respond to server list queries //so it may be possible to play on the server even if it doesn't respond to server list queries
var query = instance.client.ServerList.History(filter); var query = instance.client.ServerList.History(filter);
query.OnUpdate += () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); }; query.OnUpdate = () => { UpdateServerQuery(query, onServerFound, onServerRulesReceived, includeUnresponsive: true); };
query.OnFinished = onFinished; query.OnFinished = onFinished;
return true; return true;
} }
private static void UpdateLobbyQuery(Action<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, Action onFinished)
{
foreach (LobbyList.Lobby lobby in instance.client.LobbyList.Lobbies)
{
if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { continue; }
bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword);
int.TryParse(lobby.GetData("playercount"), out int currPlayers);
int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers);
//UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId);
string ip = lobby.GetData("hostipaddress");
if (string.IsNullOrWhiteSpace(ip)) { ip = ""; }
var serverInfo = new ServerInfo()
{
ServerName = lobby.Name,
Port = "",
IP = ip,
PlayerCount = currPlayers,
MaxPlayers = maxPlayers,
HasPassword = hasPassword,
RespondedToSteamQuery = true,
LobbyID = lobby.LobbyID
};
serverInfo.PingChecked = false;
serverInfo.ServerMessage = lobby.GetData("message");
serverInfo.GameVersion = lobby.GetData("version");
serverInfo.ContentPackageNames.AddRange(lobby.GetData("contentpackage").Split(','));
serverInfo.ContentPackageHashes.AddRange(lobby.GetData("contentpackagehash").Split(','));
serverInfo.ContentPackageWorkshopUrls.AddRange(lobby.GetData("contentpackageurl").Split(','));
serverInfo.UsingWhiteList = lobby.GetData("usingwhitelist") == "True";
SelectionMode selectionMode;
if (Enum.TryParse(lobby.GetData("modeselectionmode"), out selectionMode)) serverInfo.ModeSelectionMode = selectionMode;
if (Enum.TryParse(lobby.GetData("subselectionmode"), out selectionMode)) serverInfo.SubSelectionMode = selectionMode;
serverInfo.AllowSpectating = lobby.GetData("allowspectating") == "True";
serverInfo.AllowRespawn = lobby.GetData("allowrespawn") == "True";
serverInfo.VoipEnabled = lobby.GetData("voicechatenabled") == "True";
if (Enum.TryParse(lobby.GetData("traitors"), out YesNoMaybe traitorsEnabled)) serverInfo.TraitorsEnabled = traitorsEnabled;
serverInfo.GameStarted = lobby.GetData("gamestarted") == "True";
serverInfo.GameMode = lobby.GetData("gamemode");
if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count ||
serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopUrls.Count)
{
//invalid contentpackage info
serverInfo.ContentPackageNames.Clear();
serverInfo.ContentPackageHashes.Clear();
}
onServerFound(serverInfo);
//onServerRulesReceived(serverInfo);
}
onFinished();
}
private static void UpdateServerQuery(ServerList.Request query, Action<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, bool includeUnresponsive) private static void UpdateServerQuery(ServerList.Request query, Action<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, bool includeUnresponsive)
{ {
IEnumerable<ServerList.Server> servers = includeUnresponsive ? IEnumerable<ServerList.Server> servers = includeUnresponsive ?
@@ -191,6 +452,9 @@ namespace Barotrauma.Steam
{ {
DebugConsole.Log(s.Name + " did not respond to server query."); DebugConsole.Log(s.Name + " did not respond to server query.");
} }
if (s.Description == "Barotrauma IP Retrieval") { continue; }
var serverInfo = new ServerInfo() var serverInfo = new ServerInfo()
{ {
ServerName = s.Name, ServerName = s.Name,
@@ -203,6 +467,7 @@ namespace Barotrauma.Steam
}; };
serverInfo.PingChecked = true; serverInfo.PingChecked = true;
serverInfo.Ping = s.Ping; serverInfo.Ping = s.Ping;
serverInfo.LobbyID = 0;
if (responded) if (responded)
{ {
s.FetchRules(); s.FetchRules();
@@ -210,7 +475,7 @@ namespace Barotrauma.Steam
s.OnReceivedRules += (bool rulesReceived) => s.OnReceivedRules += (bool rulesReceived) =>
{ {
if (!rulesReceived || s.Rules == null) { return; } if (!rulesReceived || s.Rules == null) { return; }
if (s.Rules.ContainsKey("message")) serverInfo.ServerMessage = s.Rules["message"]; if (s.Rules.ContainsKey("message")) serverInfo.ServerMessage = s.Rules["message"];
if (s.Rules.ContainsKey("version")) serverInfo.GameVersion = s.Rules["version"]; if (s.Rules.ContainsKey("version")) serverInfo.GameVersion = s.Rules["version"];
@@ -270,6 +535,7 @@ namespace Barotrauma.Steam
return true; return true;
} }
private static Auth.Ticket currentTicket = null;
public static Auth.Ticket GetAuthSessionTicket() public static Auth.Ticket GetAuthSessionTicket()
{ {
if (instance == null || !instance.isInitialized) if (instance == null || !instance.isInitialized)
@@ -277,17 +543,39 @@ namespace Barotrauma.Steam
return null; return null;
} }
return instance.client.Auth.GetAuthSessionTicket(); currentTicket?.Cancel();
currentTicket = instance.client.Auth.GetAuthSessionTicket();
return currentTicket;
}
public static ClientStartAuthSessionResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{
if (instance == null || !instance.isInitialized || instance.client == null) return ClientStartAuthSessionResult.ServerNotConnectedToSteam;
DebugConsole.NewMessage("SteamManager authenticating Steam client " + clientSteamID);
ClientStartAuthSessionResult startResult = instance.client.Auth.StartSession(authTicketData, clientSteamID);
if (startResult != ClientStartAuthSessionResult.OK)
{
DebugConsole.NewMessage("Authentication failed: failed to start auth session (" + startResult.ToString() + ")");
}
return startResult;
}
public static void StopAuthSession(ulong clientSteamID)
{
if (instance == null || !instance.isInitialized || instance.client == null) return;
DebugConsole.NewMessage("SteamManager ending auth session with Steam client " + clientSteamID);
instance.client.Auth.EndSession(clientSteamID);
} }
#endregion #endregion
#region Workshop #region Workshop
public const string WorkshopItemStagingFolder = "NewWorkshopItem";
public const string WorkshopItemPreviewImageFolder = "Workshop"; public const string WorkshopItemPreviewImageFolder = "Workshop";
public const string PreviewImageName = "PreviewImage.png"; public const string PreviewImageName = "PreviewImage.png";
private const string MetadataFileName = "filelist.xml";
public const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png"; public const string DefaultPreviewImagePath = "Content/DefaultWorkshopPreviewImage.png";
private Sprite defaultPreviewImage; private Sprite defaultPreviewImage;
@@ -404,54 +692,45 @@ namespace Barotrauma.Steam
item.Subscribe(); item.Subscribe();
item.Download(); item.Download();
} }
/// <summary> public static void CreateWorkshopItemStaging(ContentPackage contentPackage, out Workshop.Editor itemEditor)
/// Creates a new folder, copies the specified files there and creates a metadata file with install instructions.
/// </summary>
public static void CreateWorkshopItemStaging(List<ContentFile> contentFiles, out Workshop.Editor itemEditor, out ContentPackage contentPackage)
{ {
var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder); itemEditor = instance.client.Workshop.CreateItem(Workshop.ItemType.Community);
if (stagingFolder.Exists) itemEditor.Visibility = Workshop.Editor.VisibilityType.Public;
itemEditor.WorkshopUploadAppId = AppID;
itemEditor.Folder = Path.GetFullPath(Path.GetDirectoryName(contentPackage.Path));
string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName));
if (!Directory.Exists(itemEditor.Folder)) { Directory.CreateDirectory(itemEditor.Folder); }
if (!File.Exists(previewImagePath))
{ {
SaveUtil.ClearFolder(stagingFolder.FullName); File.Copy("Content/DefaultWorkshopPreviewImage.png", previewImagePath);
} }
else }
{
stagingFolder.Create(); /// <summary>
} /// Creates a new empty content package
Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Submarines")); /// </summary>
Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Mods")); public static void CreateWorkshopItemStaging(string itemName, out Workshop.Editor itemEditor, out ContentPackage contentPackage)
Directory.CreateDirectory(Path.Combine(WorkshopItemStagingFolder, "Mods", "ModName")); {
string dirPath = Path.Combine("Mods", ToolBox.RemoveInvalidFileNameChars(itemName));
Directory.CreateDirectory("Mods");
Directory.CreateDirectory(dirPath);
itemEditor = instance.client.Workshop.CreateItem(Workshop.ItemType.Community); itemEditor = instance.client.Workshop.CreateItem(Workshop.ItemType.Community);
itemEditor.Visibility = Workshop.Editor.VisibilityType.Public; itemEditor.Visibility = Workshop.Editor.VisibilityType.Public;
itemEditor.WorkshopUploadAppId = AppID; itemEditor.WorkshopUploadAppId = AppID;
itemEditor.Folder = stagingFolder.FullName; itemEditor.Folder = dirPath;
string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName)); string previewImagePath = Path.GetFullPath(Path.Combine(dirPath, PreviewImageName));
File.Copy("Content/DefaultWorkshopPreviewImage.png", previewImagePath); if (!File.Exists(previewImagePath))
//copy content files to the staging folder
List<string> copiedFilePaths = new List<string>();
foreach (ContentFile file in contentFiles)
{ {
string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file.Path), Environment.CurrentDirectory); File.Copy("Content/DefaultWorkshopPreviewImage.png", previewImagePath);
string destinationPath = Path.Combine(stagingFolder.FullName, relativePath);
//make sure the directory exists
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
File.Copy(file.Path, destinationPath);
copiedFilePaths.Add(destinationPath);
} }
System.Diagnostics.Debug.Assert(copiedFilePaths.Count == contentFiles.Count);
//create a new content package and include the copied files in it //create a new content package and include the copied files in it
contentPackage = ContentPackage.CreatePackage("ContentPackage", Path.Combine(itemEditor.Folder, MetadataFileName), false); contentPackage = ContentPackage.CreatePackage(itemName, Path.Combine(dirPath, MetadataFileName), false);
for (int i = 0; i < copiedFilePaths.Count; i++) contentPackage.Save(Path.Combine(dirPath, MetadataFileName));
{
contentPackage.AddFile(copiedFilePaths[i], contentFiles[i].Type);
}
contentPackage.Save(Path.Combine(stagingFolder.FullName, MetadataFileName));
} }
/// <summary> /// <summary>
@@ -467,23 +746,67 @@ namespace Barotrauma.Steam
return; return;
} }
var stagingFolder = new DirectoryInfo(WorkshopItemStagingFolder);
if (stagingFolder.Exists)
{
SaveUtil.ClearFolder(stagingFolder.FullName);
}
else
{
stagingFolder.Create();
}
itemEditor = instance.client.Workshop.EditItem(existingItem.Id); itemEditor = instance.client.Workshop.EditItem(existingItem.Id);
itemEditor.Visibility = Workshop.Editor.VisibilityType.Public; itemEditor.Visibility = Workshop.Editor.VisibilityType.Public;
itemEditor.Title = existingItem.Title; itemEditor.Title = existingItem.Title;
itemEditor.Tags = existingItem.Tags.ToList(); itemEditor.Tags = existingItem.Tags.ToList();
itemEditor.Description = existingItem.Description; itemEditor.Description = existingItem.Description;
itemEditor.WorkshopUploadAppId = AppID; itemEditor.WorkshopUploadAppId = AppID;
itemEditor.Folder = stagingFolder.FullName;
if (!CheckWorkshopItemEnabled(existingItem, checkContentFiles: false))
{
if (!EnableWorkShopItem(existingItem, false, out string errorMsg))
{
DebugConsole.ThrowError(errorMsg);
new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { existingItem.Title, errorMsg }));
itemEditor = null;
contentPackage = null;
return;
}
}
ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem.Directory.FullName, MetadataFileName));
string installedContentPackagePath = Path.GetFullPath(GetWorkshopItemContentPackagePath(tempContentPackage));
contentPackage = ContentPackage.List.Find(cp => Path.GetFullPath(cp.Path) == installedContentPackagePath);
if (tempContentPackage.GameVersion > new Version(0, 9, 1, 0))
{
itemEditor.Folder = Path.GetDirectoryName(installedContentPackagePath);
}
else //legacy support
{
try
{
tempContentPackage.GameVersion = GameMain.Version;
string newPath = GetWorkshopItemContentPackagePath(tempContentPackage);
string newDir = Path.GetDirectoryName(newPath);
contentPackage.Path = newPath;
itemEditor.Folder = newDir;
if (!Directory.Exists(newDir)) { Directory.CreateDirectory(newDir); }
File.Move(installedContentPackagePath, newPath);
//move all files inside the Mods folder
foreach (ContentFile cf in contentPackage.Files)
{
string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(cf.Path), Path.GetFullPath(newDir));
if (relativePath.StartsWith(".."))
{
string destinationPath = Path.Combine(newDir, cf.Path);
if (!Directory.Exists(Path.GetDirectoryName(destinationPath))) { Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); }
File.Move(cf.Path, destinationPath);
cf.Path = destinationPath;
}
}
contentPackage.Save(contentPackage.Path);
}
catch (Exception e)
{
string errorMsg = TextManager.GetWithVariable("WorkshopErrorOnEnable", "[itemname]", TextManager.EnsureUTF8(existingItem.Title));
new GUIMessageBox(TextManager.Get("Error"), errorMsg);
DebugConsole.ThrowError(errorMsg, e);
return;
}
}
string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName)); string previewImagePath = Path.GetFullPath(Path.Combine(itemEditor.Folder, PreviewImageName));
itemEditor.PreviewImage = previewImagePath; itemEditor.PreviewImage = previewImagePath;
@@ -512,46 +835,6 @@ namespace Barotrauma.Steam
GameAnalyticsManager.AddErrorEventOnce("SteamManager.CreateWorkshopItemStaging:WriteAllBytesFailed" + previewImagePath, GameAnalyticsManager.AddErrorEventOnce("SteamManager.CreateWorkshopItemStaging:WriteAllBytesFailed" + previewImagePath,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + e.Message); GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + e.Message);
} }
ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem.Directory.FullName, MetadataFileName));
//item already installed, copy it from the game folder
if (existingItem != null && CheckWorkshopItemEnabled(existingItem, checkContentFiles: false))
{
string installedItemPath = GetWorkshopItemContentPackagePath(tempContentPackage);
if (File.Exists(installedItemPath))
{
tempContentPackage = new ContentPackage(installedItemPath);
}
}
if (File.Exists(tempContentPackage.Path))
{
string newContentPackagePath = Path.Combine(WorkshopItemStagingFolder, MetadataFileName);
File.Copy(tempContentPackage.Path, newContentPackagePath, overwrite: true);
contentPackage = new ContentPackage(newContentPackagePath);
foreach (ContentFile contentFile in tempContentPackage.Files)
{
string sourceFile;
if (contentFile.Type == ContentType.Submarine && File.Exists(contentFile.Path))
{
sourceFile = contentFile.Path;
}
else
{
sourceFile = Path.Combine(existingItem.Directory.FullName, contentFile.Path);
}
if (!File.Exists(sourceFile)) { continue; }
//make sure the destination directory exists
string destinationPath = Path.Combine(WorkshopItemStagingFolder, contentFile.Path);
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
File.Copy(sourceFile, destinationPath, overwrite: true);
contentPackage.AddFile(contentFile.Path, contentFile.Type);
}
}
else
{
contentPackage = ContentPackage.CreatePackage(existingItem.Title, Path.Combine(WorkshopItemStagingFolder, MetadataFileName), false);
contentPackage.Save(contentPackage.Path);
}
} }
public static void StartPublishItem(ContentPackage contentPackage, Workshop.Editor item) public static void StartPublishItem(ContentPackage contentPackage, Workshop.Editor item)
@@ -568,12 +851,11 @@ namespace Barotrauma.Steam
DebugConsole.ThrowError("Cannot publish workshop item \"" + item.Title + "\" - folder not set."); DebugConsole.ThrowError("Cannot publish workshop item \"" + item.Title + "\" - folder not set.");
return; return;
} }
contentPackage.Name = item.Title;
contentPackage.GameVersion = GameMain.Version; contentPackage.GameVersion = GameMain.Version;
contentPackage.Save(Path.Combine(WorkshopItemStagingFolder, MetadataFileName)); contentPackage.Save(contentPackage.Path);
if (File.Exists(PreviewImageName)) File.Delete(PreviewImageName); if (File.Exists(PreviewImageName)) { File.Delete(PreviewImageName); }
//move the preview image out of the staging folder, it does not need to be included in the folder sent to Workshop //move the preview image out of the staging folder, it does not need to be included in the folder sent to Workshop
File.Move(Path.GetFullPath(Path.Combine(item.Folder, PreviewImageName)), PreviewImageName); File.Move(Path.GetFullPath(Path.Combine(item.Folder, PreviewImageName)), PreviewImageName);
item.PreviewImage = Path.GetFullPath(PreviewImageName); item.PreviewImage = Path.GetFullPath(PreviewImageName);
@@ -604,18 +886,7 @@ namespace Barotrauma.Steam
{ {
DebugConsole.NewMessage("Publishing workshop item " + item.Title + " failed. " + item.Error, Microsoft.Xna.Framework.Color.Red); DebugConsole.NewMessage("Publishing workshop item " + item.Title + " failed. " + item.Error, Microsoft.Xna.Framework.Color.Red);
} }
SaveUtil.ClearFolder(WorkshopItemStagingFolder);
File.Delete(PreviewImageName);
try
{
Directory.Delete(WorkshopItemStagingFolder);
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to delete Workshop item staging folder.", e);
}
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -632,6 +903,14 @@ namespace Barotrauma.Steam
} }
string metaDataFilePath = Path.Combine(item.Directory.FullName, MetadataFileName); string metaDataFilePath = Path.Combine(item.Directory.FullName, MetadataFileName);
if (!File.Exists(metaDataFilePath))
{
errorMsg = TextManager.GetWithVariable("WorkshopErrorInstallRequiredToEnable", "[itemname]", item.Title);
DebugConsole.ThrowError(errorMsg);
return false;
}
ContentPackage contentPackage = new ContentPackage(metaDataFilePath); ContentPackage contentPackage = new ContentPackage(metaDataFilePath);
string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage); string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage);
@@ -644,11 +923,52 @@ namespace Barotrauma.Steam
if (contentPackage.CorePackage && !contentPackage.ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes)) if (contentPackage.CorePackage && !contentPackage.ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes))
{ {
errorMsg = TextManager.GetWithVariables("ContentPackageMissingCoreFiles", new string[2] { "[packagename]", "[missingfiletypes]" }, errorMsg = TextManager.GetWithVariables("ContentPackageMissingCoreFiles", new string[2] { "[packagename]", "[missingfiletypes]" },
new string[2] { contentPackage.Name, string.Join(", ", missingContentTypes) }, new bool[2] { false, true }); new string[2] { contentPackage.Name, string.Join(", ", missingContentTypes) }, new bool[2] { false, true });
return false; return false;
} }
if (contentPackage.GameVersion > new Version(0, 9, 1, 0))
{
SaveUtil.CopyFolder(item.Directory.FullName, Path.GetDirectoryName(GetWorkshopItemContentPackagePath(contentPackage)), copySubDirs: true, overwriteExisting: true);
}
else //legacy support
{
EnableWorkShopItemLegacy(item, contentPackage, newContentPackagePath, metaDataFilePath, allowFileOverwrite, out errorMsg);
}
var newPackage = new ContentPackage(contentPackage.Path, newContentPackagePath)
{
SteamWorkshopUrl = item.Url,
InstallTime = item.Modified > item.Created ? item.Modified : item.Created
};
if (!Directory.Exists(Path.GetDirectoryName(newContentPackagePath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(newContentPackagePath));
}
newPackage.Save(newContentPackagePath);
ContentPackage.List.Add(newPackage);
if (newPackage.CorePackage)
{
//if enabling a core package, disable all other core packages
GameMain.Config.SelectedContentPackages.RemoveWhere(cp => cp.CorePackage);
}
GameMain.Config.SelectedContentPackages.Add(newPackage);
GameMain.Config.SaveNewPlayerConfig();
if (newPackage.Files.Any(f => f.Type == ContentType.Submarine))
{
Submarine.RefreshSavedSubs();
}
errorMsg = "";
return true;
}
private static bool EnableWorkShopItemLegacy(Workshop.Item item, ContentPackage contentPackage, string newContentPackagePath, string metaDataFilePath, bool allowFileOverwrite, out string errorMsg)
{
errorMsg = "";
var allPackageFiles = Directory.GetFiles(item.Directory.FullName, "*", SearchOption.AllDirectories); var allPackageFiles = Directory.GetFiles(item.Directory.FullName, "*", SearchOption.AllDirectories);
List<string> nonContentFiles = new List<string>(); List<string> nonContentFiles = new List<string>();
foreach (string file in allPackageFiles) foreach (string file in allPackageFiles)
@@ -748,27 +1068,6 @@ namespace Barotrauma.Steam
return false; return false;
} }
var newPackage = new ContentPackage(contentPackage.Path, newContentPackagePath)
{
SteamWorkshopUrl = item.Url,
InstallTime = item.Modified > item.Created ? item.Modified : item.Created
};
newPackage.Save(newContentPackagePath);
ContentPackage.List.Add(newPackage);
if (newPackage.CorePackage)
{
//if enabling a core package, disable all other core packages
GameMain.Config.SelectedContentPackages.RemoveWhere(cp => cp.CorePackage);
}
GameMain.Config.SelectedContentPackages.Add(newPackage);
GameMain.Config.SaveNewPlayerConfig();
if (newPackage.Files.Any(f => f.Type == ContentType.Submarine))
{
Submarine.RefreshSavedSubs();
}
errorMsg = "";
return true; return true;
} }
@@ -894,7 +1193,14 @@ namespace Barotrauma.Steam
public static bool CheckWorkshopItemEnabled(Workshop.Item item, bool checkContentFiles = true) public static bool CheckWorkshopItemEnabled(Workshop.Item item, bool checkContentFiles = true)
{ {
if (!item.Installed) return false; if (!item.Installed) { return false; }
if (!Directory.Exists(item.Directory.FullName))
{
DebugConsole.ThrowError("Workshop item \"" + item.Title + "\" has been installed but the install directory cannot be found. Attempting to redownload...");
item.ForceDownload();
return false;
}
string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName); string metaDataPath = Path.Combine(item.Directory.FullName, MetadataFileName);
if (!File.Exists(metaDataPath)) if (!File.Exists(metaDataPath))
@@ -1004,10 +1310,11 @@ namespace Barotrauma.Steam
public static string GetWorkshopItemContentPackagePath(ContentPackage contentPackage) public static string GetWorkshopItemContentPackagePath(ContentPackage contentPackage)
{ {
string fileName = contentPackage.Name + ".xml"; string fileName = contentPackage.Name;
string invalidChars = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars()); string invalidChars = ToolBox.RemoveInvalidFileNameChars(fileName);
foreach (char c in invalidChars) fileName = fileName.Replace(c.ToString(), ""); return contentPackage.GameVersion > new Version(0, 9, 1, 0) ?
return Path.Combine("Data", "ContentPackages", fileName); Path.Combine("Mods", fileName, MetadataFileName) :
Path.Combine("Data", "ContentPackages", fileName + ".xml"); //legacy support
} }
#endregion #endregion
@@ -1,5 +1,4 @@
using Lidgren.Network; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using OpenAL; using OpenAL;
using System; using System;
using System.Linq; using System.Linq;
@@ -97,7 +96,8 @@ namespace Barotrauma.Networking
UserData = "capturedevicenotfound" UserData = "capturedevicenotfound"
}; };
} }
GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpen(" + deviceName + ") failed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,"Error code: "+errorCode); GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Alc.CaptureDeviceOpen(" + deviceName + ") failed. Error code: " + errorCode);
GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled; GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled;
Instance?.Dispose(); Instance?.Dispose();
Instance = null; Instance = null;
@@ -234,7 +234,7 @@ namespace Barotrauma.Networking
} }
} }
public override void Write(NetBuffer msg) public override void Write(IWriteMessage msg)
{ {
lock (buffers) lock (buffers)
{ {
@@ -1,5 +1,4 @@
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Lidgren.Network;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -13,13 +12,13 @@ namespace Barotrauma.Networking
class VoipClient : IDisposable class VoipClient : IDisposable
{ {
private GameClient gameClient; private GameClient gameClient;
private NetClient netClient; private ClientPeer netClient;
private DateTime lastSendTime; private DateTime lastSendTime;
private List<VoipQueue> queues; private List<VoipQueue> queues;
private UInt16 storedBufferID = 0; private UInt16 storedBufferID = 0;
public VoipClient(GameClient gClient,NetClient nClient) public VoipClient(GameClient gClient,ClientPeer nClient)
{ {
gameClient = gClient; gameClient = gClient;
netClient = nClient; netClient = nClient;
@@ -59,19 +58,19 @@ namespace Barotrauma.Networking
if (DateTime.Now >= lastSendTime + VoipConfig.SEND_INTERVAL) if (DateTime.Now >= lastSendTime + VoipConfig.SEND_INTERVAL)
{ {
NetOutgoingMessage msg = netClient.CreateMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.VOICE); msg.Write((byte)ClientPacketHeader.VOICE);
msg.Write((byte)VoipCapture.Instance.QueueID); msg.Write((byte)VoipCapture.Instance.QueueID);
VoipCapture.Instance.Write(msg); VoipCapture.Instance.Write(msg);
netClient.SendMessage(msg, NetDeliveryMethod.Unreliable); netClient.Send(msg, DeliveryMethod.Unreliable);
lastSendTime = DateTime.Now; lastSendTime = DateTime.Now;
} }
} }
public void Read(NetBuffer msg) public void Read(IReadMessage msg)
{ {
byte queueId = msg.ReadByte(); byte queueId = msg.ReadByte();
VoipQueue queue = queues.Find(q => q.QueueID == queueId); VoipQueue queue = queues.Find(q => q.QueueID == queueId);
@@ -95,7 +94,7 @@ namespace Barotrauma.Networking
client.VoipSound = new VoipSound(GameMain.SoundManager, client.VoipQueue); client.VoipSound = new VoipSound(GameMain.SoundManager, client.VoipQueue);
} }
if (client.Character != null && !client.Character.IsDead && !client.Character.IsDead && client.Character.SpeechImpediment <= 100.0f) if (client.Character != null && !client.Character.IsDead && client.Character.SpeechImpediment <= 100.0f)
{ {
var messageType = ChatMessage.CanUseRadio(client.Character, out WifiComponent radio) ? ChatMessageType.Radio : ChatMessageType.Default; var messageType = ChatMessage.CanUseRadio(client.Character, out WifiComponent radio) ? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
@@ -114,8 +113,27 @@ namespace Barotrauma.Networking
client.VoipSound.UseMuffleFilter = SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull); client.VoipSound.UseMuffleFilter = SoundPlayer.ShouldMuffleSound(Character.Controlled, client.Character.WorldPosition, ChatMessage.SpeakRange, client.Character.CurrentHull);
} }
} }
GameMain.NetLobbyScreen.SetPlayerSpeaking(client);
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client); GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
if (client.VoipSound.CurrentAmplitude > 0.1f) //TODO: might need to tweak
{
if (client.Character != null)
{
Vector3 clientPos = new Vector3(client.Character.WorldPosition.X, client.Character.WorldPosition.Y, 0.0f);
Vector3 listenerPos = GameMain.SoundManager.ListenerPosition;
float attenuationDist = client.VoipSound.Near * 1.125f;
if (Vector3.DistanceSquared(clientPos, listenerPos) < attenuationDist * attenuationDist)
{
GameMain.SoundManager.VoipAttenuatedGain = 0.5f;
}
}
else
{
GameMain.SoundManager.VoipAttenuatedGain = 0.5f;
}
}
} }
} }
@@ -1,5 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Lidgren.Network;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -105,7 +104,7 @@ namespace Barotrauma
} }
} }
public void ClientWrite(NetBuffer msg, VoteType voteType, object data) public void ClientWrite(IWriteMessage msg, VoteType voteType, object data)
{ {
msg.Write((byte)voteType); msg.Write((byte)voteType);
@@ -141,7 +140,7 @@ namespace Barotrauma
msg.WritePadBits(); msg.WritePadBits();
} }
public void ClientRead(NetBuffer inc) public void ClientRead(IReadMessage inc)
{ {
AllowSubVoting = inc.ReadBoolean(); AllowSubVoting = inc.ReadBoolean();
if (allowSubVoting) if (allowSubVoting)
@@ -1,5 +1,4 @@
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.Linq; using System.Linq;
@@ -177,7 +176,7 @@ namespace Barotrauma.Networking
return true; return true;
} }
public void ClientAdminRead(NetBuffer incMsg) public void ClientAdminRead(IReadMessage incMsg)
{ {
bool hasPermission = incMsg.ReadBoolean(); bool hasPermission = incMsg.ReadBoolean();
if (!hasPermission) if (!hasPermission)
@@ -192,8 +191,8 @@ namespace Barotrauma.Networking
incMsg.ReadPadBits(); incMsg.ReadPadBits();
whitelistedPlayers.Clear(); whitelistedPlayers.Clear();
Int32 bannedPlayerCount = incMsg.ReadVariableInt32(); UInt32 bannedPlayerCount = incMsg.ReadVariableUInt32();
for (int i = 0; i < bannedPlayerCount; i++) for (int i = 0; i < (int)bannedPlayerCount; i++)
{ {
string name = incMsg.ReadString(); string name = incMsg.ReadString();
UInt16 uniqueIdentifier = incMsg.ReadUInt16(); UInt16 uniqueIdentifier = incMsg.ReadUInt16();
@@ -216,7 +215,7 @@ namespace Barotrauma.Networking
} }
} }
public void ClientAdminWrite(NetBuffer outMsg) public void ClientAdminWrite(IWriteMessage outMsg)
{ {
outMsg.Write(localEnabled); outMsg.Write(localEnabled);
outMsg.WritePadBits(); outMsg.WritePadBits();
@@ -144,7 +144,7 @@ namespace Barotrauma
1.0f / bodyShapeTextureScale, SpriteEffects.None, 0.0f); 1.0f / bodyShapeTextureScale, SpriteEffects.None, 0.0f);
} }
public PosInfo ClientRead(ServerNetObject type, NetBuffer msg, float sendingTime, string parentDebugName) public PosInfo ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime, string parentDebugName)
{ {
float MaxVel = NetConfig.MaxPhysicsBodyVelocity; float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity; float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
@@ -156,8 +156,8 @@ namespace Barotrauma
float? newAngularVelocity = null; float? newAngularVelocity = null;
newPosition = new Vector2( newPosition = new Vector2(
msg.ReadFloat(), msg.ReadSingle(),
msg.ReadFloat()); msg.ReadSingle());
awake = msg.ReadBoolean(); awake = msg.ReadBoolean();
bool fixedRotation = msg.ReadBoolean(); bool fixedRotation = msg.ReadBoolean();
@@ -599,7 +599,8 @@ namespace Barotrauma
if (!tb.Selected) { return false; } if (!tb.Selected) { return false; }
RefreshMissionTab(tb.UserData as Mission); RefreshMissionTab(tb.UserData as Mission);
Campaign.Map.OnMissionSelected?.Invoke(connection, mission); Campaign.Map.OnMissionSelected?.Invoke(connection, mission);
if (GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign)) if ((Campaign is MultiPlayerCampaign multiPlayerCampaign) && !multiPlayerCampaign.SuppressStateSending &&
GameMain.Client != null && GameMain.Client.HasPermission(Networking.ClientPermissions.ManageCampaign))
{ {
GameMain.Client?.SendCampaignState(); GameMain.Client?.SendCampaignState();
} }
@@ -97,7 +97,7 @@ namespace Barotrauma
SoundPlayer.OverrideMusicType = "none"; SoundPlayer.OverrideMusicType = "none";
SoundPlayer.OverrideMusicDuration = null; SoundPlayer.OverrideMusicDuration = null;
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f); GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f, 0);
GUI.ForceMouseOn(null); GUI.ForceMouseOn(null);
CalculateSpritesheetPosition(); CalculateSpritesheetPosition();
@@ -195,7 +195,7 @@ namespace Barotrauma
base.Deselect(); base.Deselect();
SoundPlayer.OverrideMusicType = null; SoundPlayer.OverrideMusicType = null;
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameMain.Config.SoundVolume); GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameMain.Config.SoundVolume, 0);
GUI.ForceMouseOn(null); GUI.ForceMouseOn(null);
if (isEndlessRunner) if (isEndlessRunner)
@@ -1467,6 +1467,7 @@ namespace Barotrauma
private void ShowWearables() private void ShowWearables()
{ {
if (character.Inventory == null) { return; }
foreach (var item in character.Inventory.Items) foreach (var item in character.Inventory.Items)
{ {
if (item == null) { continue; } if (item == null) { continue; }
@@ -1478,7 +1479,7 @@ namespace Barotrauma
private void HideWearables() private void HideWearables()
{ {
character.Inventory.Items.ForEachMod(i => i?.Unequip(character)); character.Inventory?.Items.ForEachMod(i => i?.Unequip(character));
} }
#endregion #endregion
@@ -23,8 +23,8 @@ namespace Barotrauma
private CampaignSetupUI campaignSetupUI; private CampaignSetupUI campaignSetupUI;
private GUITextBox serverNameBox, portBox, queryPortBox, passwordBox, maxPlayersBox; private GUITextBox serverNameBox, /*portBox, queryPortBox,*/ passwordBox, maxPlayersBox;
private GUITickBox isPublicBox, useUpnpBox; private GUITickBox isPublicBox/*, useUpnpBox*/;
private GUIButton joinServerButton, hostServerButton, steamWorkshopButton; private GUIButton joinServerButton, hostServerButton, steamWorkshopButton;
@@ -203,10 +203,6 @@ namespace Barotrauma
UserData = Tab.SteamWorkshop, UserData = Tab.SteamWorkshop,
OnClicked = SelectTab OnClicked = SelectTab
}; };
/*#if OSX && !DEBUG
steamWorkshopButton.Text += " (Not yet available on MacOS)";
#endif*/
} }
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), customizeList.RectTransform), TextManager.Get("SubEditorButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
@@ -256,7 +252,7 @@ namespace Barotrauma
UserData = Tab.Settings, UserData = Tab.Settings,
OnClicked = SelectTab OnClicked = SelectTab
}; };
//TODO: translate
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton") new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), optionList.RectTransform), TextManager.Get("CreditsButton"), textAlignment: Alignment.Left, style: "MainMenuGUIButton")
{ {
ForceUpperCase = true, ForceUpperCase = true,
@@ -323,7 +319,7 @@ namespace Barotrauma
StartNewGame = StartGame StartNewGame = StartGame
}; };
var hostServerScale = new Vector2(0.7f, 1.0f); var hostServerScale = new Vector2(0.7f, 0.6f);
menuTabs[(int)Tab.HostServer] = new GUIFrame(new RectTransform( menuTabs[(int)Tab.HostServer] = new GUIFrame(new RectTransform(
Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale)) { RelativeOffset = relativeSpacing }); Vector2.Multiply(relativeSize, hostServerScale), GUI.Canvas, anchor, pivot, minSize.Multiply(hostServerScale), maxSize.Multiply(hostServerScale)) { RelativeOffset = relativeSpacing });
@@ -703,7 +699,7 @@ namespace Barotrauma
return false; return false;
} }
if (!int.TryParse(portBox.Text, out int port) || port < 0 || port > 65535) /*if (!int.TryParse(portBox.Text, out int port) || port < 0 || port > 65535)
{ {
portBox.Text = NetConfig.DefaultPort.ToString(); portBox.Text = NetConfig.DefaultPort.ToString();
portBox.Flash(); portBox.Flash();
@@ -720,7 +716,7 @@ namespace Barotrauma
portBox.Flash(); portBox.Flash();
return false; return false;
} }
} }*/
GameMain.NetLobbyScreen = new NetLobbyScreen(); GameMain.NetLobbyScreen = new NetLobbyScreen();
try try
@@ -732,16 +728,22 @@ namespace Barotrauma
exeName = "DedicatedServer.exe"; exeName = "DedicatedServer.exe";
} }
int ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
string arguments = "-name \"" + name.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" + string arguments = "-name \"" + name.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" +
" -port " + port.ToString() +
" -queryport " + queryPort.ToString() +
" -public " + isPublicBox.Selected.ToString() + " -public " + isPublicBox.Selected.ToString() +
" -password \"" + passwordBox.Text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" + " -password \"" + passwordBox.Text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" +
" -upnp " + useUpnpBox.Selected + " -maxplayers " + maxPlayersBox.Text;
" -maxplayers " + maxPlayersBox.Text +
" -ownerkey " + ownerKey.ToString(); int ownerKey = 0;
if (Steam.SteamManager.GetSteamID()!=0)
{
arguments += " -steamid " + Steam.SteamManager.GetSteamID();
}
else
{
ownerKey = Math.Max(CryptoRandom.Instance.Next(), 1);
arguments += " -ownerkey " + ownerKey;
}
string filename = exeName; string filename = exeName;
#if LINUX || OSX #if LINUX || OSX
@@ -752,13 +754,15 @@ namespace Barotrauma
FileName = filename, FileName = filename,
Arguments = arguments, Arguments = arguments,
#if !DEBUG #if !DEBUG
CreateNoWindow = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden WindowStyle = ProcessWindowStyle.Hidden
#endif #endif
}; };
GameMain.ServerChildProcess = Process.Start(processInfo); GameMain.ServerChildProcess = Process.Start(processInfo);
Thread.Sleep(1000); //wait until the server is ready before connecting Thread.Sleep(1000); //wait until the server is ready before connecting
GameMain.Client = new GameClient(name, "127.0.0.1:" + port.ToString(), name, ownerKey); GameMain.Client = new GameClient(name, System.Net.IPAddress.Loopback.ToString(), Steam.SteamManager.GetSteamID(), name, ownerKey, true);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -798,7 +802,10 @@ namespace Barotrauma
#else #else
joinServerButton.Enabled = true; joinServerButton.Enabled = true;
hostServerButton.Enabled = true; hostServerButton.Enabled = true;
steamWorkshopButton.Enabled = true; if (Steam.SteamManager.USE_STEAM)
{
steamWorkshopButton.Enabled = true;
}
#endif #endif
} }
@@ -976,6 +983,7 @@ namespace Barotrauma
OverflowClip = true OverflowClip = true
}; };
/* TODO: allow lidgren servers from client?
label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerPort"), textAlignment: textAlignment); label = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("ServerPort"), textAlignment: textAlignment);
portBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), textAlignment: textAlignment) portBox = new GUITextBox(new RectTransform(textFieldSize, label.RectTransform, Anchor.CenterRight), textAlignment: textAlignment)
{ {
@@ -991,7 +999,7 @@ namespace Barotrauma
Text = queryPort.ToString(), Text = queryPort.ToString(),
ToolTip = TextManager.Get("ServerQueryPortToolTip") ToolTip = TextManager.Get("ServerQueryPortToolTip")
}; };
} }*/
var maxPlayersLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("MaxPlayers"), textAlignment: textAlignment); var maxPlayersLabel = new GUITextBlock(new RectTransform(textLabelSize, parent.RectTransform), TextManager.Get("MaxPlayers"), textAlignment: textAlignment);
var buttonContainer = new GUILayoutGroup(new RectTransform(textFieldSize, maxPlayersLabel.RectTransform, Anchor.CenterRight), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(textFieldSize, maxPlayersLabel.RectTransform, Anchor.CenterRight), isHorizontal: true)
@@ -1027,10 +1035,11 @@ namespace Barotrauma
ToolTip = TextManager.Get("PublicServerToolTip") ToolTip = TextManager.Get("PublicServerToolTip")
}; };
/* TODO: remove UPnP altogether?
useUpnpBox = new GUITickBox(new RectTransform(tickBoxSize, parent.RectTransform), TextManager.Get("AttemptUPnP")) useUpnpBox = new GUITickBox(new RectTransform(tickBoxSize, parent.RectTransform), TextManager.Get("AttemptUPnP"))
{ {
ToolTip = TextManager.Get("AttemptUPnPToolTip") ToolTip = TextManager.Get("AttemptUPnPToolTip")
}; };*/
new GUIButton(new RectTransform(new Vector2(0.4f, 0.1f), menuTabs[(int)Tab.HostServer].RectTransform, Anchor.BottomRight) new GUIButton(new RectTransform(new Vector2(0.4f, 0.1f), menuTabs[(int)Tab.HostServer].RectTransform, Anchor.BottomRight)
{ {
@@ -1383,7 +1383,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) ClosePlayerFrame(btn, userdata); return true; } OnClicked = (btn, userdata) => { if (GUI.MouseOn == btn || GUI.MouseOn == btn.TextBlock) ClosePlayerFrame(btn, userdata); return true; }
}; };
Vector2 frameSize = GameMain.Client.HasPermission(ClientPermissions.ManagePermissions) ? new Vector2(.24f, .34f) : new Vector2(.24f, .14f); Vector2 frameSize = GameMain.Client.HasPermission(ClientPermissions.ManagePermissions) ? new Vector2(.24f, .44f) : new Vector2(.24f, .24f);
var playerFrameInner = new GUIFrame(new RectTransform(frameSize, playerFrame.RectTransform, Anchor.Center)); var playerFrameInner = new GUIFrame(new RectTransform(frameSize, playerFrame.RectTransform, Anchor.Center));
var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), playerFrameInner.RectTransform, Anchor.Center)) var paddedPlayerFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), playerFrameInner.RectTransform, Anchor.Center))
@@ -1530,13 +1530,29 @@ namespace Barotrauma
} }
var buttonAreaUpper = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true); var buttonAreaUpper = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true);
var buttonAreaMiddle = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true);
var buttonAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true); var buttonAreaLower = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true);
if (selectedClient.SteamID != 0 && Steam.SteamManager.IsInitialized)
{
var viewSteamProfileButton = new GUIButton(new RectTransform(new Vector2(0.8f, 1.0f), buttonAreaUpper.RectTransform, Anchor.TopCenter),
TextManager.Get("ViewSteamProfile"))
{
UserData = selectedClient
};
viewSteamProfileButton.OnClicked = (bt, userdata) =>
{
Steam.SteamManager.Instance.Overlay.OpenUrl("https://steamcommunity.com/profiles/" + selectedClient.SteamID.ToString());
return true;
};
}
if (!myClient) if (!myClient)
{ {
if (GameMain.Client.HasPermission(ClientPermissions.Ban)) if (GameMain.Client.HasPermission(ClientPermissions.Ban))
{ {
var banButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonAreaUpper.RectTransform), var banButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonAreaMiddle.RectTransform),
TextManager.Get("Ban")) TextManager.Get("Ban"))
{ {
UserData = selectedClient UserData = selectedClient
@@ -1544,7 +1560,7 @@ namespace Barotrauma
banButton.OnClicked = (bt, userdata) => { BanPlayer(selectedClient); return true; }; banButton.OnClicked = (bt, userdata) => { BanPlayer(selectedClient); return true; };
banButton.OnClicked += ClosePlayerFrame; banButton.OnClicked += ClosePlayerFrame;
var rangebanButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonAreaUpper.RectTransform), var rangebanButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonAreaMiddle.RectTransform),
TextManager.Get("BanRange")) TextManager.Get("BanRange"))
{ {
UserData = selectedClient UserData = selectedClient
@@ -1578,7 +1594,7 @@ namespace Barotrauma
kickButton.OnClicked += ClosePlayerFrame; kickButton.OnClicked += ClosePlayerFrame;
} }
new GUITickBox(new RectTransform(new Vector2(0.25f, 1.0f), buttonAreaUpper.RectTransform, Anchor.TopRight), new GUITickBox(new RectTransform(new Vector2(0.25f, 1.0f), buttonAreaMiddle.RectTransform, Anchor.TopRight),
TextManager.Get("Mute")) TextManager.Get("Mute"))
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -10,6 +10,7 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using System.Threading; using System.Threading;
namespace Barotrauma namespace Barotrauma
@@ -45,7 +46,7 @@ namespace Barotrauma
private readonly GUITickBox filterEmpty; private readonly GUITickBox filterEmpty;
private string sortedBy; private string sortedBy;
private readonly GUIButton serverPreviewToggleButton; private readonly GUIButton serverPreviewToggleButton;
//a timer for preventing the client from spamming the refresh button faster than AllowedRefreshInterval //a timer for preventing the client from spamming the refresh button faster than AllowedRefreshInterval
@@ -332,7 +333,14 @@ namespace Barotrauma
{ {
if (ipBox.UserData is ServerInfo selectedServer) if (ipBox.UserData is ServerInfo selectedServer)
{ {
JoinServer(selectedServer.IP + ":" + selectedServer.Port, selectedServer.ServerName); if (selectedServer.LobbyID == 0)
{
JoinServer(selectedServer.IP + ":" + selectedServer.Port, selectedServer.ServerName);
}
else
{
Steam.SteamManager.JoinLobby(selectedServer.LobbyID, true);
}
} }
else if (!string.IsNullOrEmpty(ipBox.Text)) else if (!string.IsNullOrEmpty(ipBox.Text))
{ {
@@ -474,6 +482,15 @@ namespace Barotrauma
RefreshServers(); RefreshServers();
} }
public override void Deselect()
{
base.Deselect();
if (SteamManager.IsInitialized && SteamManager.Instance.LobbyList != null)
{
SteamManager.Instance.LobbyList.OnLobbiesUpdated = null;
}
}
private void FilterServers() private void FilterServers()
{ {
serverList.Content.RemoveChild(serverList.Content.FindChild("noresults")); serverList.Content.RemoveChild(serverList.Content.FindChild("noresults"));
@@ -671,16 +688,24 @@ namespace Barotrauma
private void AddToServerList(ServerInfo serverInfo) private void AddToServerList(ServerInfo serverInfo)
{ {
var serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) }, var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) &&
(info.LobbyID==serverInfo.LobbyID && info.IP==serverInfo.IP && info.Port==serverInfo.Port));
if (serverFrame == null)
{
serverFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.06f), serverList.Content.RectTransform) { MinSize = new Point(0, 35) },
style: "InnerFrame", color: Color.White * 0.5f) style: "InnerFrame", color: Color.White * 0.5f)
{ {
UserData = serverInfo UserData = serverInfo
}; };
new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 1.0f), serverFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft) new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 1.0f), serverFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
Stretch = true, Stretch = true,
//RelativeSpacing = 0.02f //RelativeSpacing = 0.02f
}; };
}
serverFrame.UserData = serverInfo;
UpdateServerInfo(serverInfo); UpdateServerInfo(serverInfo);
SortList(sortedBy, toggle: false); SortList(sortedBy, toggle: false);
@@ -901,13 +926,17 @@ namespace Barotrauma
return true; return true;
} }
private IEnumerable<object> ConnectToServer(string ip, string serverName) private IEnumerable<object> ConnectToServer(string endpoint, string serverName)
{ {
string serverIP = null;
UInt64 serverSteamID = SteamManager.SteamIDStringToUInt64(endpoint);
if (serverSteamID == 0) { serverIP = endpoint; }
#if !DEBUG #if !DEBUG
try try
{ {
#endif #endif
GameMain.Client = new GameClient(clientNameBox.Text, ip, serverName); GameMain.Client = new GameClient(clientNameBox.Text, serverIP, serverSteamID, serverName);
#if !DEBUG #if !DEBUG
} }
catch (Exception e) catch (Exception e)
@@ -961,7 +990,7 @@ namespace Barotrauma
public void PingServer(ServerInfo serverInfo, int timeOut) public void PingServer(ServerInfo serverInfo, int timeOut)
{ {
if (serverInfo?.IP == null) if (string.IsNullOrWhiteSpace(serverInfo?.IP))
{ {
serverInfo.PingChecked = true; serverInfo.PingChecked = true;
serverInfo.Ping = -1; serverInfo.Ping = -1;
@@ -969,7 +998,8 @@ namespace Barotrauma
} }
long rtt = -1; long rtt = -1;
IPAddress address = IPAddress.Parse(serverInfo.IP); IPAddress address = null;
IPAddress.TryParse(serverInfo.IP, out address);
if (address != null) if (address != null)
{ {
//don't attempt to ping if the address is IPv6 and it's not supported //don't attempt to ping if the address is IPv6 and it's not supported
@@ -159,6 +159,7 @@ namespace Barotrauma
OnSelected = (component, userdata) => OnSelected = (component, userdata) =>
{ {
if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) { return false; } if (GUI.MouseOn is GUIButton || GUI.MouseOn?.Parent is GUIButton) { return false; }
if (GUI.MouseOn is GUITickBox || GUI.MouseOn?.Parent is GUITickBox) { return false; }
myItemList.Deselect(); myItemList.Deselect();
if (userdata is Facepunch.Steamworks.Workshop.Item item) if (userdata is Facepunch.Steamworks.Workshop.Item item)
{ {
@@ -190,16 +191,6 @@ namespace Barotrauma
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.5f, 0.05f), leftColumn.RectTransform), TextManager.Get("CreateWorkshopItem"))
{
OnClicked = (btn, userData) =>
{
CreateWorkshopItem();
ShowCreateItemFrame();
return true;
}
};
createItemFrame = new GUIFrame(new RectTransform(new Vector2(0.58f, 1.0f), tabs[(int)Tab.Publish].RectTransform, Anchor.TopRight), style: "InnerFrame"); createItemFrame = new GUIFrame(new RectTransform(new Vector2(0.58f, 1.0f), tabs[(int)Tab.Publish].RectTransform, Anchor.TopRight), style: "InnerFrame");
buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), container.RectTransform), childAnchor: Anchor.CenterLeft); buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), container.RectTransform), childAnchor: Anchor.CenterLeft);
@@ -269,7 +260,11 @@ namespace Barotrauma
}); });
SteamManager.GetPopularWorkshopItems((items) => { OnItemsReceived(items, topItemList); }, 20); SteamManager.GetPopularWorkshopItems((items) => { OnItemsReceived(items, topItemList); }, 20);
SteamManager.GetPublishedWorkshopItems((items) => { OnItemsReceived(items, publishedItemList); }); SteamManager.GetPublishedWorkshopItems((items) => { OnItemsReceived(items, publishedItemList); });
RefreshMyItemList();
}
private void RefreshMyItemList()
{
myItemList.ClearChildren(); myItemList.ClearChildren();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), myItemList.Content.RectTransform), TextManager.Get("WorkshopLabelSubmarines"), textAlignment: Alignment.Center, font: GUI.LargeFont) new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), myItemList.Content.RectTransform), TextManager.Get("WorkshopLabelSubmarines"), textAlignment: Alignment.Center, font: GUI.LargeFont)
{ {
@@ -278,12 +273,28 @@ namespace Barotrauma
foreach (Submarine sub in Submarine.SavedSubmarines) foreach (Submarine sub in Submarine.SavedSubmarines)
{ {
if (sub.HasTag(SubmarineTag.HideInMenus)) { continue; } if (sub.HasTag(SubmarineTag.HideInMenus)) { continue; }
//ignore subs that are part of some content package
string subPath = Path.GetFullPath(sub.FilePath); string subPath = Path.GetFullPath(sub.FilePath);
if (ContentPackage.List.Any(cp => cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath)))
//ignore subs that are part of the vanilla content package
if (GameMain.VanillaContent != null &&
GameMain.VanillaContent.GetFilesOfType(ContentType.Submarine).Any(s => Path.GetFullPath(s) == subPath))
{ {
continue; continue;
} }
//ignore subs that are part of a workshop content package
if (ContentPackage.List.Any(cp => !string.IsNullOrEmpty(cp.SteamWorkshopUrl) &&
cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath)))
{
continue;
}
//ignore subs that are defined in a content package with more files than just the sub
//(these will be listed in the "content packages" section)
if (ContentPackage.List.Any(cp => cp.Files.Count > 1 &&
cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath)))
{
continue;
}
CreateMyItemFrame(sub, myItemList); CreateMyItemFrame(sub, myItemList);
} }
@@ -294,6 +305,8 @@ namespace Barotrauma
foreach (ContentPackage contentPackage in ContentPackage.List) foreach (ContentPackage contentPackage in ContentPackage.List)
{ {
if (!string.IsNullOrEmpty(contentPackage.SteamWorkshopUrl) || contentPackage.HideInWorkshopMenu) { continue; } if (!string.IsNullOrEmpty(contentPackage.SteamWorkshopUrl) || contentPackage.HideInWorkshopMenu) { continue; }
//don't list content packages that only define one sub (they're visible in the "Submarines" section)
if (contentPackage.Files.Count == 1 && contentPackage.Files[0].Type == ContentType.Submarine) { continue; }
CreateMyItemFrame(contentPackage, myItemList); CreateMyItemFrame(contentPackage, myItemList);
} }
} }
@@ -459,6 +472,7 @@ namespace Barotrauma
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
item.UnSubscribe(); item.UnSubscribe();
subscribedItemList.RemoveChild(subscribedItemList.Content.GetChildByUserData(item));
return true; return true;
} }
}; };
@@ -787,34 +801,40 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), modificationDate.RectTransform, Anchor.CenterRight), item.Modified.ToString("dd.MM.yyyy"), textAlignment: Alignment.TopRight); new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), modificationDate.RectTransform, Anchor.CenterRight), item.Modified.ToString("dd.MM.yyyy"), textAlignment: Alignment.TopRight);
} }
private void CreateWorkshopItem() /*private void CreateWorkshopItem()
{ {
SteamManager.CreateWorkshopItemStaging(new List<ContentFile>(), out itemEditor, out itemContentPackage); SteamManager.CreateWorkshopItemStaging("ModName", out itemEditor, out itemContentPackage);
} }*/
private void CreateWorkshopItem(Submarine sub) private void CreateWorkshopItem(Submarine sub)
{ {
SteamManager.CreateWorkshopItemStaging(new List<ContentFile>(), out itemEditor, out itemContentPackage); string destinationFolder = Path.Combine("Mods", sub.Name);
itemContentPackage = ContentPackage.CreatePackage(sub.Name, Path.Combine(destinationFolder, SteamManager.MetadataFileName), corePackage: false);
SteamManager.CreateWorkshopItemStaging(itemContentPackage, out itemEditor);
string destinationPath = Path.Combine(SteamManager.WorkshopItemStagingFolder, "Submarines", Path.GetFileName(sub.FilePath)); string submarineDir = Path.GetDirectoryName(sub.FilePath);
try if (submarineDir != Path.GetDirectoryName(destinationFolder))
{ {
File.Copy(sub.FilePath, destinationPath); string destinationPath = Path.Combine(destinationFolder, Path.GetFileName(sub.FilePath));
if (!File.Exists(destinationPath))
{
File.Move(sub.FilePath, destinationPath);
}
sub.FilePath = destinationPath;
} }
catch (Exception e)
{ itemContentPackage.AddFile(sub.FilePath, ContentType.Submarine);
DebugConsole.ThrowError("Failed to copy submarine file \"" + sub.FilePath + "\" to the Workshop item staging folder.", e);
return;
}
itemContentPackage.AddFile(Path.Combine("Submarines", Path.GetFileName(sub.FilePath)), ContentType.Submarine);
itemContentPackage.Name = sub.Name; itemContentPackage.Name = sub.Name;
itemContentPackage.Save(itemContentPackage.Path);
ContentPackage.List.Add(itemContentPackage);
GameMain.Config.SelectedContentPackages.Add(itemContentPackage);
itemEditor.Title = sub.Name; itemEditor.Title = sub.Name;
itemEditor.Tags.Add("Submarine"); itemEditor.Tags.Add("Submarine");
itemEditor.Description = sub.Description; itemEditor.Description = sub.Description;
if (sub.PreviewImage != null) if (sub.PreviewImage != null)
{ {
string previewImagePath = Path.GetFullPath(Path.Combine(SteamManager.WorkshopItemStagingFolder, SteamManager.PreviewImageName)); string previewImagePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(itemContentPackage.Path), SteamManager.PreviewImageName));
try try
{ {
using (Stream s = File.Create(previewImagePath)) using (Stream s = File.Create(previewImagePath))
@@ -837,8 +857,13 @@ namespace Barotrauma
} }
private void CreateWorkshopItem(ContentPackage contentPackage) private void CreateWorkshopItem(ContentPackage contentPackage)
{ {
SteamManager.CreateWorkshopItemStaging(new List<ContentFile>(), out itemEditor, out itemContentPackage); //SteamManager.CreateWorkshopItemStaging(new List<ContentFile>(), out itemEditor, out itemContentPackage);
string modDirectory = "";
itemContentPackage = contentPackage;
SteamManager.CreateWorkshopItemStaging(itemContentPackage, out itemEditor);
itemEditor.Title = contentPackage.Name;
/*string modDirectory = "";
foreach (ContentFile file in contentPackage.Files) foreach (ContentFile file in contentPackage.Files)
{ {
itemContentPackage.AddFile(file.Path, file.Type); itemContentPackage.AddFile(file.Path, file.Type);
@@ -858,12 +883,8 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(modDirectory)) if (!string.IsNullOrEmpty(modDirectory))
{ {
SaveUtil.CopyFolder(Path.Combine("Mods", modDirectory), Path.Combine(SteamManager.WorkshopItemStagingFolder, "Mods", modDirectory), copySubDirs: true); SaveUtil.CopyFolder(Path.Combine("Mods", modDirectory), Path.Combine(SteamManager.WorkshopItemStagingFolder, "Mods", modDirectory), copySubDirs: true);
} }*/
itemContentPackage.CorePackage = contentPackage.CorePackage;
itemContentPackage.Name = contentPackage.Name;
itemContentPackage.Save(itemContentPackage.Path);
itemEditor.Title = contentPackage.Name;
} }
private void CreateWorkshopItem(Facepunch.Steamworks.Workshop.Item item) private void CreateWorkshopItem(Facepunch.Steamworks.Workshop.Item item)
{ {
@@ -879,8 +900,16 @@ namespace Barotrauma
private void ShowCreateItemFrame() private void ShowCreateItemFrame()
{ {
createItemFrame.ClearChildren(); createItemFrame.ClearChildren();
if (itemEditor == null) { return; }
if (itemEditor == null) return; if (itemContentPackage == null)
{
string errorMsg = "Failed to edit workshop item (content package null)\n" + Environment.StackTrace;
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("SteamWorkshopScreen.ShowCreateItemFrame:ContentPackageNull", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
var createItemContent = new GUILayoutGroup(new RectTransform(new Vector2(0.92f, 0.92f), createItemFrame.RectTransform, Anchor.Center)) var createItemContent = new GUILayoutGroup(new RectTransform(new Vector2(0.92f, 0.92f), createItemFrame.RectTransform, Anchor.Center))
{ {
@@ -987,7 +1016,7 @@ namespace Barotrauma
Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog() Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog()
{ {
Multiselect = true, Multiselect = true,
InitialDirectory = Path.GetFullPath(SteamManager.WorkshopItemStagingFolder), InitialDirectory = Path.GetFullPath(Path.GetDirectoryName(itemContentPackage.Path)),
Filter = TextManager.Get("WorkshopItemPreviewImage") + "|*.png", Filter = TextManager.Get("WorkshopItemPreviewImage") + "|*.png",
Title = TextManager.Get("WorkshopItemPreviewImageDialogTitle") Title = TextManager.Get("WorkshopItemPreviewImageDialogTitle")
}; };
@@ -1018,9 +1047,19 @@ namespace Barotrauma
return true; return true;
} }
}; };
//if preview image has not been set, but there's a PreviewImage file inside the mod folder, use that by default
if (string.IsNullOrEmpty(itemEditor.PreviewImage))
{
string previewImagePath = Path.Combine(Path.GetDirectoryName(itemContentPackage.Path), SteamManager.PreviewImageName);
if (File.Exists(previewImagePath))
{
itemEditor.PreviewImage = Path.GetFullPath(previewImagePath);
}
}
if (!string.IsNullOrEmpty(itemEditor.PreviewImage)) if (!string.IsNullOrEmpty(itemEditor.PreviewImage))
{ {
itemEditor.PreviewImage = Path.GetFullPath(itemEditor.PreviewImage);
if (itemPreviewSprites.ContainsKey(itemEditor.PreviewImage)) if (itemPreviewSprites.ContainsKey(itemEditor.PreviewImage))
{ {
itemPreviewSprites[itemEditor.PreviewImage].Remove(); itemPreviewSprites[itemEditor.PreviewImage].Remove();
@@ -1068,7 +1107,7 @@ namespace Barotrauma
new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), fileListTitle.RectTransform, Anchor.CenterRight), TextManager.Get("WorkshopItemShowFolder")) new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), fileListTitle.RectTransform, Anchor.CenterRight), TextManager.Get("WorkshopItemShowFolder"))
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
OnClicked = (btn, userdata) => { System.Diagnostics.Process.Start(Path.GetFullPath(SteamManager.WorkshopItemStagingFolder)); return true; } OnClicked = (btn, userdata) => { System.Diagnostics.Process.Start(Path.GetFullPath(Path.GetDirectoryName(itemContentPackage.Path))); return true; }
}; };
createItemFileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.35f), createItemContent.RectTransform)); createItemFileList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.35f), createItemContent.RectTransform));
RefreshCreateItemFileList(); RefreshCreateItemFileList();
@@ -1077,11 +1116,11 @@ namespace Barotrauma
{ {
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform, Anchor.TopRight), TextManager.Get("WorkshopItemRefreshFileList")) new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform, Anchor.TopRight), TextManager.Get("WorkshopItemRefreshFileList"))
{ {
ToolTip = TextManager.Get("WorkshopItemRefreshFileListTooltip"), ToolTip = TextManager.Get("WorkshopItemRefreshFileListTooltip"),
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
itemContentPackage = new ContentPackage(itemContentPackage.Path); itemContentPackage = new ContentPackage(itemContentPackage.Path);
RefreshCreateItemFileList(); RefreshCreateItemFileList();
@@ -1096,26 +1135,27 @@ namespace Barotrauma
{ {
Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog() Barotrauma.OpenFileDialog ofd = new Barotrauma.OpenFileDialog()
{ {
InitialDirectory = Path.GetFullPath(SteamManager.WorkshopItemStagingFolder), InitialDirectory = Path.GetFullPath(Path.GetDirectoryName(itemContentPackage.Path)),
Title = TextManager.Get("workshopitemaddfiles"), Title = TextManager.Get("workshopitemaddfiles"),
Multiselect = true Multiselect = true
}; };
if (ofd.ShowDialog() == DialogResult.OK) if (ofd.ShowDialog() == DialogResult.OK)
{ {
OnAddFilesSelected(ofd.FileNames); OnAddFilesSelected(ofd.FileNames);
RefreshMyItemList();
} }
} }
catch catch
{ {
//use a custom prompt if OpenFileDialog fails (Linux/Mac) //use a custom prompt if OpenFileDialog fails (Linux/Mac)
var msgBox = new GUIMessageBox(TextManager.Get("workshopitemaddfiles"), "", relativeSize: new Vector2(0.4f, 0.2f), var msgBox = new GUIMessageBox(TextManager.Get("workshopitemaddfiles"), "", relativeSize: new Vector2(0.4f, 0.2f),
buttons: new string[] { TextManager.Get("Cancel"), TextManager.Get("OK") }); buttons: new string[] { TextManager.Get("Cancel"), TextManager.Get("OK") });
var pathBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform, Anchor.Center) { MinSize = new Point(0, 25) }); var pathBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform, Anchor.Center) { MinSize = new Point(0, 25) });
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close; msgBox.Buttons[1].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += (btn2, userdata2) => msgBox.Buttons[1].OnClicked += (btn2, userdata2) =>
{ {
if (string.IsNullOrEmpty(pathBox?.Text)) { return true; } if (string.IsNullOrEmpty(pathBox?.Text)) { return true; }
string[] filePaths = pathBox.Text.Split(','); string[] filePaths = pathBox.Text.Split(',');
@@ -1131,6 +1171,7 @@ namespace Barotrauma
} }
}; };
//the item has been already published if it has a non-zero ID -> allow adding a changenote //the item has been already published if it has a non-zero ID -> allow adding a changenote
if (itemEditor.Id > 0) if (itemEditor.Id > 0)
{ {
@@ -1181,6 +1222,9 @@ namespace Barotrauma
itemEditor = null; itemEditor = null;
SelectTab(Tab.Browse); SelectTab(Tab.Browse);
deleteVerification.Close(); deleteVerification.Close();
createItemFrame.ClearChildren();
itemContentPackage.SteamWorkshopUrl = "";
itemContentPackage.Save(itemContentPackage.Path);
return true; return true;
}; };
deleteVerification.Buttons[1].OnClicked = deleteVerification.Close; deleteVerification.Buttons[1].OnClicked = deleteVerification.Close;
@@ -1221,7 +1265,7 @@ namespace Barotrauma
private void OnPreviewImageSelected(GUIImage previewImageElement, string filePath) private void OnPreviewImageSelected(GUIImage previewImageElement, string filePath)
{ {
string previewImagePath = Path.GetFullPath(Path.Combine(SteamManager.WorkshopItemStagingFolder, SteamManager.PreviewImageName)); string previewImagePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(itemContentPackage.Path), SteamManager.PreviewImageName));
if (new FileInfo(filePath).Length > 1024 * 1024) if (new FileInfo(filePath).Length > 1024 * 1024)
{ {
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("WorkshopItemPreviewImageTooLarge")); new GUIMessageBox(TextManager.Get("Error"), TextManager.Get("WorkshopItemPreviewImageTooLarge"));
@@ -1248,33 +1292,36 @@ namespace Barotrauma
if (fileNames == null) { return; } if (fileNames == null) { return; }
for (int i = 0; i < fileNames.Length; i++) for (int i = 0; i < fileNames.Length; i++)
{ {
string file = fileNames[i]; string file = fileNames[i]?.Trim();
if (string.IsNullOrEmpty(file)) { continue; } if (string.IsNullOrEmpty(file) || !File.Exists(file)) { continue; }
file = file.Trim();
if (!File.Exists(file)) { continue; }
string filePathRelativeToStagingFolder = UpdaterUtil.GetRelativePath(file, Path.Combine(Environment.CurrentDirectory, SteamManager.WorkshopItemStagingFolder)); string modFolder = Path.GetDirectoryName(itemContentPackage.Path);
string filePathRelativeToBaseFolder = UpdaterUtil.GetRelativePath(file, Environment.CurrentDirectory); string filePathRelativeToModFolder = UpdaterUtil.GetRelativePath(file, Path.Combine(Environment.CurrentDirectory, modFolder));
//file is not inside the staging folder string destinationPath = Path.Combine(modFolder, Path.GetFileName(file));
if (filePathRelativeToStagingFolder.StartsWith(".."))
//file is not inside the mod folder, we need to move it
if (filePathRelativeToModFolder.StartsWith("..") ||
Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file))
{ {
//submarines can be included in the content package directly string tryPath = destinationPath;
string basePath = Path.GetDirectoryName(filePathRelativeToBaseFolder.Replace("..", "")); //add a number to the filename if a file with the same name already exists
if (basePath == "Submarines") i = 2;
while (File.Exists(destinationPath))
{
destinationPath = Path.Combine(modFolder, $"{Path.GetFileNameWithoutExtension(file)} ({i}){Path.GetExtension(file)}");
i++;
}
try
{ {
string destinationPath = Path.Combine(SteamManager.WorkshopItemStagingFolder, "Submarines", Path.GetFileName(file));
File.Copy(file, destinationPath); File.Copy(file, destinationPath);
itemContentPackage.AddFile(filePathRelativeToBaseFolder, ContentType.Submarine);
} }
else catch (Exception e)
{ {
itemContentPackage.AddFile(filePathRelativeToBaseFolder, ContentType.None); DebugConsole.ThrowError("Copying the file \""+file+"\" to the mod folder failed.", e);
return;
} }
} }
else itemContentPackage.AddFile(destinationPath, ContentType.None);
{
itemContentPackage.AddFile(filePathRelativeToStagingFolder, ContentType.None);
}
} }
itemContentPackage.Save(itemContentPackage.Path); itemContentPackage.Save(itemContentPackage.Path);
RefreshCreateItemFileList(); RefreshCreateItemFileList();
@@ -1289,9 +1336,9 @@ namespace Barotrauma
foreach (ContentFile contentFile in itemContentPackage.Files) foreach (ContentFile contentFile in itemContentPackage.Files)
{ {
bool illegalPath = !ContentPackage.IsModFilePathAllowed(contentFile); bool illegalPath = !ContentPackage.IsModFilePathAllowed(contentFile);
string pathInStagingFolder = Path.Combine(SteamManager.WorkshopItemStagingFolder, contentFile.Path); //string pathInStagingFolder = Path.Combine(SteamManager.WorkshopItemStagingFolder, contentFile.Path);
bool fileInStagingFolder = File.Exists(pathInStagingFolder); //bool fileInStagingFolder = File.Exists(pathInStagingFolder);
bool fileExists = illegalPath ? File.Exists(contentFile.Path) : fileInStagingFolder; bool fileExists = File.Exists(contentFile.Path);
var fileFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.12f), createItemFileList.Content.RectTransform) { MinSize = new Point(0, 20) }, var fileFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.12f), createItemFileList.Content.RectTransform) { MinSize = new Point(0, 20) },
style: "ListBoxElement") style: "ListBoxElement")
@@ -1309,7 +1356,7 @@ namespace Barotrauma
{ {
Selected = fileExists && !illegalPath, Selected = fileExists && !illegalPath,
Enabled = false, Enabled = false,
ToolTip = TextManager.Get(fileInStagingFolder ? "WorkshopItemFileIncluded" : "WorkshopItemFileNotIncluded") ToolTip = TextManager.Get(illegalPath ? "WorkshopItemFileNotIncluded" : "WorkshopItemFileIncluded")
}; };
var nameText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), content.RectTransform, Anchor.CenterLeft), contentFile.Path, font: GUI.SmallFont) var nameText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), content.RectTransform, Anchor.CenterLeft), contentFile.Path, font: GUI.SmallFont)
@@ -1352,6 +1399,7 @@ namespace Barotrauma
itemContentPackage.RemoveFile(contentFile); itemContentPackage.RemoveFile(contentFile);
itemContentPackage.Save(itemContentPackage.Path); itemContentPackage.Save(itemContentPackage.Path);
RefreshCreateItemFileList(); RefreshCreateItemFileList();
RefreshMyItemList();
return true; return true;
} }
}; };
@@ -1408,8 +1456,8 @@ namespace Barotrauma
if (errorMsg == null) if (errorMsg == null)
{ {
new GUIMessageBox( new GUIMessageBox(
TextManager.Get("Error"), TextManager.Get("Error"),
TextManager.GetWithVariable("WorkshopItemPublishFailed", "[itemname]", TextManager.EnsureUTF8(item.Title)) + item.Error); TextManager.GetWithVariable("WorkshopItemPublishFailed", "[itemname]", TextManager.EnsureUTF8(item.Title)) + " " + item.Error);
} }
else else
{ {
@@ -1418,6 +1466,7 @@ namespace Barotrauma
} }
createItemFrame.ClearChildren(); createItemFrame.ClearChildren();
RefreshItemLists();
SelectTab(Tab.Browse); SelectTab(Tab.Browse);
} }
@@ -646,8 +646,8 @@ namespace Barotrauma
SoundPlayer.OverrideMusicType = "none"; SoundPlayer.OverrideMusicType = "none";
SoundPlayer.OverrideMusicDuration = null; SoundPlayer.OverrideMusicDuration = null;
GameMain.SoundManager.SetCategoryGainMultiplier("default", 0.0f); GameMain.SoundManager.SetCategoryGainMultiplier("default", 0.0f, 0);
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f); GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", 0.0f, 0);
linkedSubBox.ClearChildren(); linkedSubBox.ClearChildren();
foreach (Submarine sub in Submarine.SavedSubmarines) foreach (Submarine sub in Submarine.SavedSubmarines)
@@ -688,8 +688,8 @@ namespace Barotrauma
if (WiringMode) SetWiringMode(false); if (WiringMode) SetWiringMode(false);
SoundPlayer.OverrideMusicType = null; SoundPlayer.OverrideMusicType = null;
GameMain.SoundManager.SetCategoryGainMultiplier("default", GameMain.Config.SoundVolume); GameMain.SoundManager.SetCategoryGainMultiplier("default", GameMain.Config.SoundVolume, 0);
GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameMain.Config.SoundVolume); GameMain.SoundManager.SetCategoryGainMultiplier("waterambience", GameMain.Config.SoundVolume, 0);
if (dummyCharacter != null) if (dummyCharacter != null)
{ {
@@ -1000,7 +1000,8 @@ namespace Barotrauma
submarineDescriptionCharacterCount = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), descriptionHeaderGroup.RectTransform), string.Empty, textAlignment: Alignment.TopRight); submarineDescriptionCharacterCount = new GUITextBlock(new RectTransform(new Vector2(.5f, 1f), descriptionHeaderGroup.RectTransform), string.Empty, textAlignment: Alignment.TopRight);
var descriptionContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform)); var descriptionContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform));
descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform, Anchor.Center), font: GUI.SmallFont, wrap: true); descriptionBox = new GUITextBox(new RectTransform(Vector2.One, descriptionContainer.Content.RectTransform, Anchor.Center), font: GUI.SmallFont, wrap: true, textAlignment: Alignment.TopLeft);
descriptionBox.Padding = new Vector4(10 * GUI.Scale);
descriptionBox.OnTextChanged += (textBox, text) => descriptionBox.OnTextChanged += (textBox, text) =>
{ {
@@ -1189,11 +1190,13 @@ namespace Barotrauma
var contentPackList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f - contentPackagesLabel.RectTransform.RelativeSize.Y), var contentPackList = new GUIListBox(new RectTransform(new Vector2(0.5f, 1.0f - contentPackagesLabel.RectTransform.RelativeSize.Y),
horizontalArea.RectTransform, Anchor.BottomRight)); horizontalArea.RectTransform, Anchor.BottomRight));
List<string> contentPacks = Submarine.MainSub.RequiredContentPackages.ToList(); List<string> contentPacks = Submarine.MainSub.RequiredContentPackages.ToList();
foreach (ContentPackage contentPack in ContentPackage.List) foreach (ContentPackage contentPack in ContentPackage.List)
{ {
if (!contentPacks.Contains(contentPack.Name)) contentPacks.Add(contentPack.Name); //don't show content packages that only define submarine files
//(it doesn't make sense to require another sub to be installed to install this one)
if (contentPack.Files.All(cp => cp.Type == ContentType.Submarine)) { continue; }
if (!contentPacks.Contains(contentPack.Name)) { contentPacks.Add(contentPack.Name); }
} }
foreach (string contentPackageName in contentPacks) foreach (string contentPackageName in contentPacks)
@@ -2371,11 +2374,7 @@ namespace Barotrauma
sub.UpdateTransform(); sub.UpdateTransform();
} }
spriteBatch.Begin(SpriteSortMode.BackToFront, spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: cam.Transform);
BlendState.AlphaBlend,
null, null, null, null,
cam.Transform);
graphics.Clear(new Color(0.051f, 0.149f, 0.271f, 1.0f)); graphics.Clear(new Color(0.051f, 0.149f, 0.271f, 1.0f));
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
@@ -2383,7 +2382,12 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch, new Vector2(cam.WorldView.X, -Submarine.MainSub.HiddenSubPosition.Y), new Vector2(cam.WorldView.Right, -Submarine.MainSub.HiddenSubPosition.Y), Color.White * 0.5f, 1.0f, (int)(2.0f / cam.Zoom)); GUI.DrawLine(spriteBatch, new Vector2(cam.WorldView.X, -Submarine.MainSub.HiddenSubPosition.Y), new Vector2(cam.WorldView.Right, -Submarine.MainSub.HiddenSubPosition.Y), Color.White * 0.5f, 1.0f, (int)(2.0f / cam.Zoom));
} }
Submarine.Draw(spriteBatch, true); Submarine.DrawBack(spriteBatch, editing: true);
spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.AlphaBlend, transformMatrix: cam.Transform);
Submarine.DrawFront(spriteBatch, editing: true);
if (!CharacterMode && !WiringMode && GUI.MouseOn == null) if (!CharacterMode && !WiringMode && GUI.MouseOn == null)
{ {
@@ -12,6 +12,9 @@ namespace Barotrauma.Sounds
//key = sample rate, value = filter //key = sample rate, value = filter
private static Dictionary<int, BiQuad> muffleFilters = new Dictionary<int, BiQuad>(); private static Dictionary<int, BiQuad> muffleFilters = new Dictionary<int, BiQuad>();
private static List<float> playbackAmplitude;
private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file
public OggSound(SoundManager owner, string filename, bool stream) : base(owner, filename, stream, true) public OggSound(SoundManager owner, string filename, bool stream) : base(owner, filename, stream, true)
{ {
if (!ToolBox.IsProperFilenameCase(filename)) if (!ToolBox.IsProperFilenameCase(filename))
@@ -32,6 +35,18 @@ namespace Barotrauma.Sounds
short[] shortBuffer = new short[bufferSize]; short[] shortBuffer = new short[bufferSize];
int readSamples = reader.ReadSamples(floatBuffer, 0, bufferSize); int readSamples = reader.ReadSamples(floatBuffer, 0, bufferSize);
playbackAmplitude = new List<float>();
for (int i=0;i<bufferSize;i+=reader.Channels*AMPLITUDE_SAMPLE_COUNT)
{
float maxAmplitude = 0.0f;
for (int j=i;j<i+reader.Channels*AMPLITUDE_SAMPLE_COUNT;j++)
{
if (j >= bufferSize) { break; }
maxAmplitude = Math.Max(maxAmplitude, Math.Abs(floatBuffer[j]));
}
playbackAmplitude.Add(maxAmplitude);
}
CastBuffer(floatBuffer, shortBuffer, readSamples); CastBuffer(floatBuffer, shortBuffer, readSamples);
@@ -61,6 +76,15 @@ namespace Barotrauma.Sounds
} }
} }
public override float GetAmplitudeAtPlaybackPos(int playbackPos)
{
if (playbackAmplitude == null || playbackAmplitude.Count == 0) { return 0.0f; }
int index = playbackPos / AMPLITUDE_SAMPLE_COUNT;
if (index < 0) { return 0.0f; }
if (index >= playbackAmplitude.Count) { index = playbackAmplitude.Count - 1; }
return playbackAmplitude[index];
}
public override int FillStreamBuffer(int samplePos, short[] buffer) public override int FillStreamBuffer(int samplePos, short[] buffer)
{ {
if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!");
@@ -179,6 +179,8 @@ namespace Barotrauma.Sounds
public abstract int FillStreamBuffer(int samplePos, short[] buffer); public abstract int FillStreamBuffer(int samplePos, short[] buffer);
public abstract float GetAmplitudeAtPlaybackPos(int playbackPos);
public virtual void Dispose() public virtual void Dispose()
{ {
if (disposed) { return; } if (disposed) { return; }
@@ -2,6 +2,7 @@
using OpenAL; using OpenAL;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace Barotrauma.Sounds namespace Barotrauma.Sounds
{ {
@@ -79,7 +80,7 @@ namespace Barotrauma.Sounds
public class SoundChannel : IDisposable public class SoundChannel : IDisposable
{ {
private const int STREAM_BUFFER_SIZE = 65536; private const int STREAM_BUFFER_SIZE = 8820;
private short[] streamShortBuffer; private short[] streamShortBuffer;
private Vector3? position; private Vector3? position;
@@ -282,6 +283,35 @@ namespace Barotrauma.Sounds
} }
} }
private float streamAmplitude;
public float CurrentAmplitude
{
get
{
if (!IsPlaying) { return 0.0f; }
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
if (!IsStream)
{
int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos);
int alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to get source's playback position: " + Al.GetErrorString(alError));
}
return Sound.GetAmplitudeAtPlaybackPos(playbackPos);
}
else
{
float retVal = -1.0f;
Monitor.Enter(mutex);
retVal = streamAmplitude;
Monitor.Exit(mutex);
return retVal;
}
}
}
private string category; private string category;
public string Category public string Category
{ {
@@ -311,10 +341,12 @@ namespace Barotrauma.Sounds
private set; private set;
} }
private int streamSeekPos; private int streamSeekPos;
private bool startedPlaying; private int buffersToRequeue;
private bool reachedEndSample; private bool reachedEndSample;
private int queueStartIndex;
private readonly uint[] streamBuffers; private readonly uint[] streamBuffers;
private readonly List<uint> emptyBuffers; private uint[] unqueuedBuffers;
private float[] streamBufferAmplitudes;
private object mutex; private object mutex;
@@ -346,12 +378,17 @@ namespace Barotrauma.Sounds
FilledByNetwork = sound is VoipSound; FilledByNetwork = sound is VoipSound;
decayTimer = 0; decayTimer = 0;
streamSeekPos = 0; reachedEndSample = false; streamSeekPos = 0; reachedEndSample = false;
startedPlaying = true; buffersToRequeue = 4;
muffled = muffle;
mutex = new object();
lock (mutex) if (IsStream)
{ {
mutex = new object();
}
try
{
if (mutex != null) { Monitor.Enter(mutex); }
if (sound.Owner.CountPlayingInstances(sound) < sound.MaxSimultaneousInstances) if (sound.Owner.CountPlayingInstances(sound) < sound.MaxSimultaneousInstances)
{ {
ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this); ALSourceIndex = sound.Owner.AssignFreeSourceToChannel(this);
@@ -390,7 +427,8 @@ namespace Barotrauma.Sounds
} }
else else
{ {
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)sound.ALBuffer); uint alBuffer = sound.Owner.GetCategoryMuffle(category) || muffle ? sound.ALMuffledBuffer : sound.ALBuffer;
Al.Sourcei(sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex), Al.Buffer, (int)alBuffer);
int alError = Al.GetError(); int alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
{ {
@@ -407,7 +445,8 @@ namespace Barotrauma.Sounds
streamShortBuffer = new short[STREAM_BUFFER_SIZE]; streamShortBuffer = new short[STREAM_BUFFER_SIZE];
streamBuffers = new uint[4]; streamBuffers = new uint[4];
emptyBuffers = new List<uint>(); unqueuedBuffers = new uint[4];
streamBufferAmplitudes = new float[4];
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
Al.GenBuffer(out streamBuffers[i]); Al.GenBuffer(out streamBuffers[i]);
@@ -423,18 +462,27 @@ namespace Barotrauma.Sounds
throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid!"); throw new Exception("Generated streamBuffer[" + i.ToString() + "] is invalid!");
} }
} }
Sound.Owner.InitStreamThread(); Sound.Owner.InitStreamThread();
} }
} }
this.Position = position; this.Position = position;
this.Gain = gain; this.Gain = gain;
this.Looping = false; this.Looping = false;
this.Near = near; this.Near = near;
this.Far = far; this.Far = far;
this.Category = category; this.Category = category;
} }
catch
{
throw;
}
finally
{
if (mutex != null) { Monitor.Exit(mutex); }
}
Sound.Owner.Update();
} }
public bool FadingOutAndDisposing; public bool FadingOutAndDisposing;
@@ -445,8 +493,9 @@ namespace Barotrauma.Sounds
public void Dispose() public void Dispose()
{ {
lock (mutex) try
{ {
if (mutex != null) { Monitor.Enter(mutex); }
if (ALSourceIndex >= 0) if (ALSourceIndex >= 0)
{ {
Al.SourceStop(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex)); Al.SourceStop(Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex));
@@ -467,19 +516,17 @@ namespace Barotrauma.Sounds
throw new Exception("Failed to stop streamed source: " + Al.GetErrorString(alError)); throw new Exception("Failed to stop streamed source: " + Al.GetErrorString(alError));
} }
int buffersToUnqueue = 0; int buffersToRequeue = 0;
uint[] unqueuedBuffers = null;
buffersToRequeue = 0;
buffersToUnqueue = 0; Al.GetSourcei(alSource, Al.BuffersProcessed, out buffersToRequeue);
Al.GetSourcei(alSource, Al.BuffersProcessed, out buffersToUnqueue);
alError = Al.GetError(); alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
{ {
throw new Exception("Failed to determine processed buffers from streamed source: " + Al.GetErrorString(alError)); throw new Exception("Failed to determine processed buffers from streamed source: " + Al.GetErrorString(alError));
} }
unqueuedBuffers = new uint[buffersToUnqueue]; Al.SourceUnqueueBuffers(alSource, buffersToRequeue, unqueuedBuffers);
Al.SourceUnqueueBuffers(alSource, buffersToUnqueue, unqueuedBuffers);
alError = Al.GetError(); alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
{ {
@@ -518,14 +565,19 @@ namespace Barotrauma.Sounds
ALSourceIndex = -1; ALSourceIndex = -1;
} }
} }
finally
{
if (mutex != null) { Monitor.Exit(mutex); }
}
} }
public void UpdateStream() public void UpdateStream()
{ {
if (!IsStream) throw new Exception("Called UpdateStream on a non-streamed sound channel!"); if (!IsStream) throw new Exception("Called UpdateStream on a non-streamed sound channel!");
lock (mutex) try
{ {
Monitor.Enter(mutex);
if (!reachedEndSample) if (!reachedEndSample)
{ {
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex); uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
@@ -538,48 +590,38 @@ namespace Barotrauma.Sounds
{ {
throw new Exception("Failed to determine playing state from streamed source: " + Al.GetErrorString(alError)); throw new Exception("Failed to determine playing state from streamed source: " + Al.GetErrorString(alError));
} }
int buffersToUnqueue = 0;
uint[] unqueuedBuffers = null;
if (!startedPlaying)
{
buffersToUnqueue = 0;
Al.GetSourcei(alSource, Al.BuffersProcessed, out buffersToUnqueue);
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to determine processed buffers from streamed source: " + Al.GetErrorString(alError));
}
unqueuedBuffers = new uint[buffersToUnqueue+emptyBuffers.Count]; int unqueuedBufferCount;
Al.SourceUnqueueBuffers(alSource, buffersToUnqueue, unqueuedBuffers); Al.GetSourcei(alSource, Al.BuffersProcessed, out unqueuedBufferCount);
for (int i = 0; i < emptyBuffers.Count; i++) alError = Al.GetError();
{ if (alError != Al.NoError)
unqueuedBuffers[buffersToUnqueue + i] = emptyBuffers[i];
}
buffersToUnqueue += emptyBuffers.Count;
emptyBuffers.Clear();
alError = Al.GetError();
if (alError != Al.NoError)
{
throw new Exception("Failed to unqueue buffers from streamed source: " + Al.GetErrorString(alError));
}
}
else
{ {
startedPlaying = false; throw new Exception("Failed to determine processed buffers from streamed source: " + Al.GetErrorString(alError));
buffersToUnqueue = 4; }
unqueuedBuffers = new uint[4];
for (int i = 0; i < 4; i++) Al.SourceUnqueueBuffers(alSource, unqueuedBufferCount, unqueuedBuffers);
{ alError = Al.GetError();
unqueuedBuffers[i] = streamBuffers[i]; if (alError != Al.NoError)
} {
throw new Exception("Failed to unqueue buffers from streamed source: " + Al.GetErrorString(alError));
} }
for (int i = 0; i < buffersToUnqueue; i++) buffersToRequeue += unqueuedBufferCount;
int iterCount = buffersToRequeue;
for (int k = 0; k < iterCount; k++)
{ {
int index = queueStartIndex;
short[] buffer = streamShortBuffer; short[] buffer = streamShortBuffer;
int readSamples = Sound.FillStreamBuffer(streamSeekPos, buffer); int readSamples = Sound.FillStreamBuffer(streamSeekPos, buffer);
float readAmplitude = 0.0f;
for (int i=0;i<Math.Min(readSamples, buffer.Length);i++)
{
float sampleF = ((float)buffer[i]) / ((float)short.MaxValue);
readAmplitude = Math.Max(readAmplitude, Math.Abs(sampleF));
}
if (FilledByNetwork) if (FilledByNetwork)
{ {
if (Sound is VoipSound voipSound) if (Sound is VoipSound voipSound)
@@ -589,6 +631,7 @@ namespace Barotrauma.Sounds
if (readSamples <= 0) if (readSamples <= 0)
{ {
streamAmplitude *= 0.5f;
decayTimer++; decayTimer++;
if (decayTimer > 120) //TODO: replace magic number if (decayTimer > 120) //TODO: replace magic number
{ {
@@ -618,38 +661,54 @@ namespace Barotrauma.Sounds
if (readSamples > 0) if (readSamples > 0)
{ {
Al.BufferData<short>(unqueuedBuffers[i], Sound.ALFormat, buffer, readSamples, Sound.SampleRate); streamBufferAmplitudes[index] = readAmplitude;
Al.BufferData<short>(streamBuffers[index], Sound.ALFormat, buffer, readSamples, Sound.SampleRate);
alError = Al.GetError(); alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
{ {
throw new Exception("Failed to assign data to stream buffer: " + throw new Exception("Failed to assign data to stream buffer: " +
Al.GetErrorString(alError) + ": " + unqueuedBuffers[i].ToString() + "/" + unqueuedBuffers.Length + ", readSamples: " + readSamples); Al.GetErrorString(alError) + ": " + streamBuffers[index].ToString() + "/" + streamBuffers.Length + ", readSamples: " + readSamples);
} }
Al.SourceQueueBuffer(alSource, unqueuedBuffers[i]); Al.SourceQueueBuffer(alSource, streamBuffers[index]);
queueStartIndex = (queueStartIndex + 1) % 4;
alError = Al.GetError(); alError = Al.GetError();
if (alError != Al.NoError) if (alError != Al.NoError)
{ {
throw new Exception("Failed to queue buffer[" + i.ToString() + "] to stream: " + Al.GetErrorString(alError)); throw new Exception("Failed to queue streamBuffer[" + index.ToString() + "] to stream: " + Al.GetErrorString(alError));
} }
} }
else if (readSamples < 0)
{
reachedEndSample = true;
}
else else
{ {
emptyBuffers.Add((uint)unqueuedBuffers[i]); if (readSamples < 0)
{
reachedEndSample = true;
}
break;
} }
buffersToRequeue--;
} }
streamAmplitude = streamBufferAmplitudes[queueStartIndex];
Al.GetSourcei(alSource, Al.SourceState, out state); Al.GetSourcei(alSource, Al.SourceState, out state);
if (state != Al.Playing) if (state != Al.Playing)
{ {
Al.SourcePlay(alSource); Al.SourcePlay(alSource);
} }
} }
if (reachedEndSample)
{
streamAmplitude = 0.0f;
}
}
finally
{
Monitor.Exit(mutex);
} }
} }
} }
@@ -100,6 +100,47 @@ namespace Barotrauma.Sounds
} }
} }
} }
public float PlaybackAmplitude
{
get
{
float aggregateAmplitude = 0.0f;
//NOTE: this is obviously not entirely accurate;
//It assumes a linear falloff model, and assumes that audio
//is simply added together to produce the final result.
//Adjustments may be needed under certain scenarios.
for (int i=0;i<2;i++)
{
foreach (SoundChannel soundChannel in playingChannels[i].Where(ch => ch != null))
{
float amplitude = soundChannel.CurrentAmplitude;
amplitude *= soundChannel.Gain;
float dist = Vector3.Distance(ListenerPosition, soundChannel.Position ?? ListenerPosition);
if (dist > soundChannel.Near)
{
amplitude *= 1.0f - Math.Min(1.0f, (dist - soundChannel.Near) / (soundChannel.Far - soundChannel.Near));
}
aggregateAmplitude += amplitude;
}
}
return aggregateAmplitude;
}
}
public float CompressionDynamicRangeGain { get; private set; }
private float voipAttenuatedGain;
private double lastAttenuationTime;
public float VoipAttenuatedGain
{
get { return voipAttenuatedGain; }
set
{
lastAttenuationTime = Timing.TotalTime;
voipAttenuatedGain = value;
}
}
public int LoadedSoundCount public int LoadedSoundCount
{ {
@@ -110,7 +151,43 @@ namespace Barotrauma.Sounds
get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); } get { return loadedSounds.Select(s => s.Filename).Distinct().Count(); }
} }
private Dictionary<string, Pair<float, bool>> categoryModifiers; private class CategoryModifier
{
public float[] GainMultipliers;
public bool Muffle;
public CategoryModifier(int gainMultiplierIndex, float gain, bool muffle)
{
Muffle = muffle;
GainMultipliers = new float[gainMultiplierIndex+1];
for (int i=0;i<GainMultipliers.Length;i++)
{
if (i==gainMultiplierIndex)
{
GainMultipliers[i] = gain;
}
else
{
GainMultipliers[i] = 1.0f;
}
}
}
public void SetGainMultiplier(int index, float gain)
{
if (GainMultipliers.Length < index+1)
{
int oldLength = GainMultipliers.Length;
Array.Resize(ref GainMultipliers, index + 1);
for (int i=oldLength;i<GainMultipliers.Length;i++)
{
GainMultipliers[i] = 1.0f;
}
}
GainMultipliers[index] = gain;
}
}
private Dictionary<string, CategoryModifier> categoryModifiers;
public SoundManager() public SoundManager()
{ {
@@ -190,6 +267,8 @@ namespace Barotrauma.Sounds
ListenerPosition = Vector3.Zero; ListenerPosition = Vector3.Zero;
ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f); ListenerTargetVector = new Vector3(0.0f, 0.0f, 1.0f);
ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f); ListenerUpVector = new Vector3(0.0f, -1.0f, 0.0f);
CompressionDynamicRangeGain = 1.0f;
} }
public Sound LoadSound(string filename, bool stream = false) public Sound LoadSound(string filename, bool stream = false)
@@ -257,11 +336,12 @@ namespace Barotrauma.Sounds
{ {
if (Disabled) { return -1; } if (Disabled) { return -1; }
lock (playingChannels) //remove a channel that has stopped
//or hasn't even been assigned
int poolIndex = (int)newChannel.Sound.SourcePoolIndex;
lock (playingChannels[poolIndex])
{ {
//remove a channel that has stopped
//or hasn't even been assigned
int poolIndex = (int)newChannel.Sound.SourcePoolIndex;
for (int i = 0; i < playingChannels[poolIndex].Length; i++) for (int i = 0; i < playingChannels[poolIndex].Length; i++)
{ {
if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying) if (playingChannels[poolIndex][i] == null || !playingChannels[poolIndex][i].IsPlaying)
@@ -271,10 +351,10 @@ namespace Barotrauma.Sounds
return i; return i;
} }
} }
//we couldn't get a free source to assign to this channel!
return -1;
} }
//we couldn't get a free source to assign to this channel!
return -1;
} }
#if DEBUG #if DEBUG
@@ -291,7 +371,7 @@ namespace Barotrauma.Sounds
public bool IsPlaying(Sound sound) public bool IsPlaying(Sound sound)
{ {
if (Disabled) { return false; } if (Disabled) { return false; }
lock (playingChannels) lock (playingChannels[(int)sound.SourcePoolIndex])
{ {
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++) for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{ {
@@ -309,7 +389,7 @@ namespace Barotrauma.Sounds
{ {
if (Disabled) { return 0; } if (Disabled) { return 0; }
int count = 0; int count = 0;
lock (playingChannels) lock (playingChannels[(int)sound.SourcePoolIndex])
{ {
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++) for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{ {
@@ -326,7 +406,7 @@ namespace Barotrauma.Sounds
public SoundChannel GetChannelFromSound(Sound sound) public SoundChannel GetChannelFromSound(Sound sound)
{ {
if (Disabled) { return null; } if (Disabled) { return null; }
lock (playingChannels) lock (playingChannels[(int)sound.SourcePoolIndex])
{ {
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++) for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{ {
@@ -343,7 +423,7 @@ namespace Barotrauma.Sounds
public void KillChannels(Sound sound) public void KillChannels(Sound sound)
{ {
if (Disabled) { return; } if (Disabled) { return; }
lock (playingChannels) lock (playingChannels[(int)sound.SourcePoolIndex])
{ {
for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++) for (int i = 0; i < playingChannels[(int)sound.SourcePoolIndex].Length; i++)
{ {
@@ -371,22 +451,23 @@ namespace Barotrauma.Sounds
} }
} }
public void SetCategoryGainMultiplier(string category, float gain) public void SetCategoryGainMultiplier(string category, float gain, int index=0)
{ {
if (Disabled) { return; } if (Disabled) { return; }
category = category.ToLower(); category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, Pair<float, bool>>(); if (categoryModifiers == null) categoryModifiers = new Dictionary<string, CategoryModifier>();
if (!categoryModifiers.ContainsKey(category)) if (!categoryModifiers.ContainsKey(category))
{ {
categoryModifiers.Add(category, new Pair<float, bool>(gain, false)); categoryModifiers.Add(category, new CategoryModifier(index, gain, false));
} }
else else
{ {
categoryModifiers[category].First = gain; categoryModifiers[category].SetGainMultiplier(index, gain);
} }
lock (playingChannels)
for (int i = 0; i < playingChannels.Length; i++)
{ {
for (int i = 0; i < playingChannels.Length; i++) lock (playingChannels[i])
{ {
for (int j = 0; j < playingChannels[i].Length; j++) for (int j = 0; j < playingChannels[i].Length; j++)
{ {
@@ -399,12 +480,24 @@ namespace Barotrauma.Sounds
} }
} }
public float GetCategoryGainMultiplier(string category) public float GetCategoryGainMultiplier(string category, int index=-1)
{ {
if (Disabled) { return 0.0f; } if (Disabled) { return 0.0f; }
category = category.ToLower(); category = category.ToLower();
if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return 1.0f; if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return 1.0f;
return categoryModifiers[category].First; if (index < 0)
{
float accumulatedMultipliers = 1.0f;
for (int i=0;i<categoryModifiers[category].GainMultipliers.Length;i++)
{
accumulatedMultipliers *= categoryModifiers[category].GainMultipliers[i];
}
return accumulatedMultipliers;
}
else
{
return categoryModifiers[category].GainMultipliers[index];
}
} }
public void SetCategoryMuffle(string category,bool muffle) public void SetCategoryMuffle(string category,bool muffle)
@@ -413,23 +506,26 @@ namespace Barotrauma.Sounds
category = category.ToLower(); category = category.ToLower();
if (categoryModifiers == null) categoryModifiers = new Dictionary<string, Pair<float, bool>>(); if (categoryModifiers == null) categoryModifiers = new Dictionary<string, CategoryModifier>();
if (!categoryModifiers.ContainsKey(category)) if (!categoryModifiers.ContainsKey(category))
{ {
categoryModifiers.Add(category, new Pair<float, bool>(1.0f, muffle)); categoryModifiers.Add(category, new CategoryModifier(0, 1.0f, muffle));
} }
else else
{ {
categoryModifiers[category].Second = muffle; categoryModifiers[category].Muffle = muffle;
} }
for (int i = 0; i < playingChannels.Length; i++) for (int i = 0; i < playingChannels.Length; i++)
{ {
for (int j = 0; j < playingChannels[i].Length; j++) lock (playingChannels[i])
{ {
if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying) for (int j = 0; j < playingChannels[i].Length; j++)
{ {
if (playingChannels[i][j].Category.ToLower() == category) playingChannels[i][j].Muffled = muffle; if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
{
if (playingChannels[i][j].Category.ToLower() == category) playingChannels[i][j].Muffled = muffle;
}
} }
} }
} }
@@ -441,7 +537,45 @@ namespace Barotrauma.Sounds
category = category.ToLower(); category = category.ToLower();
if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return false; if (categoryModifiers == null || !categoryModifiers.ContainsKey(category)) return false;
return categoryModifiers[category].Second; return categoryModifiers[category].Muffle;
}
public void Update()
{
if (GameMain.Client != null && GameMain.Config.VoipAttenuationEnabled)
{
if (Timing.TotalTime > lastAttenuationTime+0.2)
{
voipAttenuatedGain = voipAttenuatedGain * 0.9f + 0.1f;
}
}
else
{
voipAttenuatedGain = 1.0f;
}
SetCategoryGainMultiplier("default", VoipAttenuatedGain, 1);
SetCategoryGainMultiplier("ui", VoipAttenuatedGain, 1);
SetCategoryGainMultiplier("waterambience", VoipAttenuatedGain, 1);
SetCategoryGainMultiplier("music", VoipAttenuatedGain, 1);
if (GameMain.Config.DynamicRangeCompressionEnabled)
{
float targetGain = (Math.Min(1.0f, 1.0f / PlaybackAmplitude) - 1.0f) * 0.5f + 1.0f;
if (targetGain < CompressionDynamicRangeGain)
{
//if the target gain is lower than the current gain, lower the current gain immediately to prevent clipping
CompressionDynamicRangeGain = targetGain;
}
else
{
//otherwise, let it rise back smoothly
CompressionDynamicRangeGain = (targetGain) * 0.05f + CompressionDynamicRangeGain * 0.95f;
}
}
else
{
CompressionDynamicRangeGain = 1.0f;
}
} }
public void InitStreamThread() public void InitStreamThread()
@@ -464,9 +598,10 @@ namespace Barotrauma.Sounds
while (areStreamsPlaying) while (areStreamsPlaying)
{ {
areStreamsPlaying = false; areStreamsPlaying = false;
lock (playingChannels)
for (int i = 0; i < playingChannels.Length; i++)
{ {
for (int i = 0; i < playingChannels.Length; i++) lock (playingChannels[i])
{ {
for (int j = 0; j < playingChannels[i].Length; j++) for (int j = 0; j < playingChannels[i].Length; j++)
{ {
@@ -497,14 +632,14 @@ namespace Barotrauma.Sounds
Thread.Sleep(10); //TODO: use a separate thread for network audio? Thread.Sleep(10); //TODO: use a separate thread for network audio?
} }
} }
public void Dispose() public void Dispose()
{ {
if (Disabled) { return; } if (Disabled) { return; }
lock (playingChannels) for (int i = 0; i < playingChannels.Length; i++)
{ {
for (int i = 0; i < playingChannels.Length; i++) lock (playingChannels[i])
{ {
for (int j = 0; j < playingChannels[i].Length; j++) for (int j = 0; j < playingChannels[i].Length; j++)
{ {
@@ -512,6 +647,7 @@ namespace Barotrauma.Sounds
} }
} }
} }
streamingThread?.Join(); streamingThread?.Join();
for (int i = loadedSounds.Count - 1; i >= 0; i--) for (int i = loadedSounds.Count - 1; i >= 0; i--)
{ {
@@ -393,19 +393,19 @@ namespace Barotrauma
if (Math.Abs(diff.X) < FireSoundRange && Math.Abs(diff.Y) < FireSoundRange) if (Math.Abs(diff.X) < FireSoundRange && Math.Abs(diff.Y) < FireSoundRange)
{ {
Vector2 diffLeft = (fs.WorldPosition + new Vector2(fs.Size.X, fs.Size.Y / 2)) - listenerPos; Vector2 diffLeft = (fs.WorldPosition + new Vector2(fs.Size.X, fs.Size.Y / 2)) - listenerPos;
if (diff.X < fs.Size.X / 2.0f) diff.X = 0.0f; if (Math.Abs(diff.X) < fs.Size.X / 2.0f) { diffLeft.X = 0.0f; }
if (diffLeft.X <= 0) if (diffLeft.X <= 0)
{ {
float distFallOffLeft = diffLeft.Length() / FireSoundRange; float distFallOffLeft = diffLeft.Length() / FireSoundRange;
if (distFallOffLeft < 0.99f) if (distFallOffLeft < 0.99f)
{ {
fireVolumeLeft[0] += (1.0f - distFallOffLeft) * (fs.Size.X / FireSoundLargeLimit); fireVolumeLeft[0] += (1.0f - distFallOffLeft);
if (fs.Size.X > FireSoundLargeLimit) fireVolumeLeft[1] += (1.0f - distFallOffLeft) * ((fs.Size.X - FireSoundLargeLimit) / FireSoundLargeLimit); if (fs.Size.X > FireSoundLargeLimit) fireVolumeLeft[1] += (1.0f - distFallOffLeft) * ((fs.Size.X - FireSoundLargeLimit) / FireSoundLargeLimit);
} }
} }
Vector2 diffRight = (fs.WorldPosition + new Vector2(0.0f, fs.Size.Y / 2)) - listenerPos; Vector2 diffRight = (fs.WorldPosition + new Vector2(0.0f, fs.Size.Y / 2)) - listenerPos;
if (diff.X < fs.Size.X / 2.0f) diff.X = 0.0f; if (Math.Abs(diff.X) < fs.Size.X / 2.0f) { diffRight.X = 0.0f; }
if (diffRight.X >= 0) if (diffRight.X >= 0)
{ {
float distFallOffRight = diffRight.Length() / FireSoundRange; float distFallOffRight = diffRight.Length() / FireSoundRange;
@@ -32,6 +32,11 @@ namespace Barotrauma.Sounds
video = vid; video = vid;
} }
public override float GetAmplitudeAtPlaybackPos(int playbackPos)
{
throw new NotImplementedException();
}
public override bool IsPlaying() public override bool IsPlaying()
{ {
bool retVal = false; bool retVal = false;
@@ -65,8 +70,15 @@ namespace Barotrauma.Sounds
SoundChannel chn = null; SoundChannel chn = null;
lock (mutex) lock (mutex)
{ {
if (soundChannel != null) soundChannel.Dispose(); if (soundChannel != null)
chn = new SoundChannel(this, gain, null, 1.0f, 3.0f, "video", false); {
soundChannel.Dispose();
soundChannel = null;
}
}
chn = new SoundChannel(this, gain, null, 1.0f, 3.0f, "video", false);
lock (mutex)
{
soundChannel = chn; soundChannel = chn;
} }
return chn; return chn;
@@ -25,7 +25,7 @@ namespace Barotrauma.Sounds
} }
private VoipQueue queue; private VoipQueue queue;
public int bufferID = 0; private int bufferID = 0;
private SoundChannel soundChannel; private SoundChannel soundChannel;
@@ -54,6 +54,11 @@ namespace Barotrauma.Sounds
} }
} }
public float CurrentAmplitude
{
get { return soundChannel?.CurrentAmplitude ?? 0.0f; }
}
public VoipSound(SoundManager owner, VoipQueue q) : base(owner, "voip", true, true) public VoipSound(SoundManager owner, VoipQueue q) : base(owner, "voip", true, true)
{ {
VoipConfig.SetupEncoding(); VoipConfig.SetupEncoding();
@@ -70,6 +75,11 @@ namespace Barotrauma.Sounds
soundChannel = chn; soundChannel = chn;
} }
public override float GetAmplitudeAtPlaybackPos(int playbackPos)
{
throw new NotImplementedException(); //TODO: implement?
}
public void SetPosition(Vector3? pos) public void SetPosition(Vector3? pos)
{ {
soundChannel.Position = pos; soundChannel.Position = pos;
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
@@ -75,14 +76,13 @@ namespace Barotrauma
} }
} }
public void ReloadTexture() public void ReloadTexture(bool updateAllSprites = false) => ReloadTexture(updateAllSprites ? LoadedSprites.Where(s => s.Texture == texture) : new Sprite[] { this });
{
var sprites = LoadedSprites.Where(s => s.Texture == texture).ToList();
texture.Dispose();
texture = null;
public void ReloadTexture(IEnumerable<Sprite> spritesToUpdate)
{
texture.Dispose();
texture = TextureLoader.FromFile(FilePath, preMultipliedAlpha); texture = TextureLoader.FromFile(FilePath, preMultipliedAlpha);
foreach (Sprite sprite in sprites) foreach (Sprite sprite in spritesToUpdate)
{ {
sprite.texture = texture; sprite.texture = texture;
} }
@@ -100,15 +100,19 @@ namespace Barotrauma
return Color.Lerp(gradient[(int)scaledT], gradient[(int)Math.Min(scaledT + 1, gradient.Length - 1)], (scaledT - (int)scaledT)); return Color.Lerp(gradient[(int)scaledT], gradient[(int)Math.Min(scaledT + 1, gradient.Length - 1)], (scaledT - (int)scaledT));
} }
public static string WrapText(string text, float lineLength, ScalableFont font, float textScale = 1.0f) //TODO: could integrate this into the ScalableFont class directly public static string WrapText(string text, float lineLength, ScalableFont font, float textScale = 1.0f, bool playerInput = false) //TODO: could integrate this into the ScalableFont class directly
{ {
Vector2 textSize = font.MeasureString(text); Vector2 textSize = font.MeasureString(text);
if (textSize.X < lineLength) { return text; } if (textSize.X < lineLength) { return text; }
text = text.Replace("\n", " \n "); if (!playerInput)
{
text = text.Replace("\n", " \n ");
}
List<string> words = new List<string>(); List<string> words = new List<string>();
string currWord = ""; string currWord = "";
for (int i = 0; i < text.Length; i++) for (int i = 0; i < text.Length; i++)
{ {
if (TextManager.IsCJK(text[i].ToString())) if (TextManager.IsCJK(text[i].ToString()))
@@ -127,6 +131,7 @@ namespace Barotrauma
words.Add(currWord); words.Add(currWord);
currWord = ""; currWord = "";
} }
words.Add(string.Empty);
} }
else else
{ {
@@ -144,69 +149,128 @@ namespace Barotrauma
Vector2 spaceSize = font.MeasureString(" ") * textScale; Vector2 spaceSize = font.MeasureString(" ") * textScale;
for (int i = 0; i < words.Count; ++i) for (int i = 0; i < words.Count; ++i)
{ {
if (words[i].Length == 0) string currentWord = words[i];
if (currentWord.Length == 0)
{ {
//space // space
currentWord = " ";
} }
else if (string.IsNullOrWhiteSpace(words[i]) && words[i] != "\n") else if (string.IsNullOrWhiteSpace(currentWord) && currentWord != "\n")
{ {
continue; continue;
} }
Vector2 size = words[i].Length == 0 ? spaceSize : font.MeasureString(words[i]) * textScale; Vector2 size = words[i].Length == 0 ? spaceSize : font.MeasureString(currentWord) * textScale;
if (size.X > lineLength) if (size.X > lineLength)
{ {
if (linePos == 0.0f) float splitSize = 0.0f;
List<string> splitWord = new List<string>() { string.Empty };
int k = 0;
for (int j = 0; j < currentWord.Length; j++)
{ {
wrappedText.AppendLine(words[i]); splitWord[k] += currentWord[j];
} splitSize += (font.MeasureString(currentWord[j].ToString()) * textScale).X;
else
{ if (splitSize + linePos > lineLength)
do
{ {
if (words[i].Length == 0) break; linePos = splitSize = 0.0f;
splitWord[k] = splitWord[k].Remove(splitWord[k].Length - 1) + "\n";
wrappedText.Append(words[i][0]); j--;
words[i] = words[i].Remove(0, 1); splitWord.Add(string.Empty);
k++;
linePos += size.X; }
} while (words[i].Length > 0 && (size = font.MeasureString((words[i][0]).ToString()) * textScale).X + linePos < lineLength);
wrappedText.Append("\n");
linePos = 0.0f;
i--;
} }
continue; for (int j = 0; j < splitWord.Count; j++)
}
if (linePos + size.X < lineLength)
{
wrappedText.Append(words[i]);
if (words[i] == "\n")
{ {
linePos = 0.0f; wrappedText.Append(splitWord[j]);
}
else
{
linePos += size.X + spaceSize.X;
} }
linePos = splitSize;
} }
else else
{ {
wrappedText.Append("\n"); if (linePos + size.X < lineLength)
wrappedText.Append(words[i]); {
wrappedText.Append(currentWord);
if (currentWord == "\n")
{
linePos = 0.0f;
}
else
{
linePos += size.X;
}
}
else
{
wrappedText.Append("\n");
wrappedText.Append(currentWord);
linePos = size.X + spaceSize.X; linePos = size.X;
} }
if (i < words.Count - 1 && !TextManager.IsCJK(words[i]) && !TextManager.IsCJK(words[i + 1]))
{
wrappedText.Append(" ");
} }
} }
return wrappedText.ToString().Replace(" \n ", "\n"); if (!playerInput)
} {
return wrappedText.ToString().Replace(" \n ", "\n");
}
else
{
return wrappedText.ToString();
}
}
public static void ParseConnectCommand(string[] args, out string name, out string endpoint, out UInt64 lobbyId)
{
name = null; endpoint = null; lobbyId = 0;
for (int i = 0; i < args.Length - 1; i++)
{
if (i < args.Length-2 && args[i].Trim().ToLower().Equals("-connect", StringComparison.InvariantCultureIgnoreCase))
{
int j = i + 2;
name = "";
if (args[i + 1].Trim()[0] == '"')
{
name = args[i + 1].Trim().Substring(1);
if (!(name[name.Length - 1] == '"' && (name.Length < 2 || name[name.Length - 1] != '\\')))
{
for (; j < args.Length - 1; j++)
{
name += " " + args[j].Trim();
if (name[name.Length - 1] == '"' && (name.Length < 2 || name[name.Length - 1] != '\\'))
{
name = name.Substring(0, name.Length - 1).Replace("\\\"", "\"");
j++;
break;
}
}
}
else
{
name = name.Substring(0, name.Length - 1);
}
}
else
{
name = args[i + 1].Trim();
}
endpoint = args[j].Trim();
break;
}
else if (args[i].Trim().ToLower().Equals("+connect_lobby", StringComparison.InvariantCultureIgnoreCase))
{
UInt64.TryParse(args[i + 1].Trim(), out lobbyId);
endpoint = null;
name = null;
break;
}
}
}
} }
} }

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