Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2025-04-10 10:37:09 -03:00
296 changed files with 8420 additions and 2945 deletions

View File

@@ -40,12 +40,12 @@ namespace Barotrauma.Networking
{
if (string.IsNullOrEmpty(value)) { return; }
Name = value;
nameId++;
ForceNameJobTeamUpdate();
}
public void ForceNameJobTeamUpdate()
{
// Triggers SendLobbyUpdate() which causes the server to call GameServer.ClientReadLobby()
// Deviously triggers SendLobbyUpdate() which causes the server to call GameServer.ClientReadLobby()
nameId++;
}
@@ -584,6 +584,9 @@ namespace Barotrauma.Networking
private readonly List<IReadMessage> pendingIncomingMessages = new List<IReadMessage>();
private readonly List<IReadMessage> incomingMessagesToProcess = new List<IReadMessage>();
private CoroutineHandle startGameCoroutine;
private bool requestNewRoundStart;
private void ReadDataMessage(IReadMessage inc)
{
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
@@ -718,9 +721,6 @@ namespace Barotrauma.Networking
campaignUpdateIDs[flag] = inc.ReadUInt16();
}
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
if (campaign != null) { campaign.PendingSubmarineSwitch = null; }
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
bool readyToStart;
@@ -742,13 +742,9 @@ namespace Barotrauma.Networking
campaign.LastSaveID == campaignSaveID &&
campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value);
}
readyToStartMsg.WriteBoolean(readyToStart);
DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start.");
WriteCharacterInfo(readyToStartMsg);
ClientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable);
SendStartGameResponse(readyToStart: readyToStart);
if (readyToStart && !CoroutineManager.IsCoroutineRunning("WaitForStartRound"))
{
@@ -775,15 +771,34 @@ namespace Barotrauma.Networking
break;
case ServerPacketHeader.STARTGAME:
DebugConsole.Log("Received STARTGAME packet.");
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
if (GameMain.NetLobbyScreen is not { AFKSelected: true } || !ServerSettings.AllowAFK)
{
//start without a loading screen if playing a campaign round
CoroutineManager.StartCoroutine(StartGame(inc));
if (startGameCoroutine != null && CoroutineManager.IsCoroutineRunning(startGameCoroutine))
{
DebugConsole.Log("New round started before the previous one had finished loading. Starting a new round once loading the round finishes...");
requestNewRoundStart = true;
}
else
{
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
{
//start without a loading screen if playing a campaign round
DebugConsole.Log($"Starting {nameof(StartGame)} coroutine...");
startGameCoroutine = CoroutineManager.StartCoroutine(StartGame(inc));
}
else
{
GUIMessageBox.CloseAll();
DebugConsole.Log($"Starting {nameof(StartGame)} coroutine with a loading screen...");
startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false);
}
}
}
else
{
GUIMessageBox.CloseAll();
GameMain.Instance.ShowLoading(StartGame(inc), false);
//reselect to refresh the state of the screen (to indicate the round is running)
GameStarted = true;
GameMain.NetLobbyScreen?.Select();
}
break;
case ServerPacketHeader.STARTGAMEFINALIZE:
@@ -930,10 +945,19 @@ namespace Barotrauma.Networking
contentToPreload.AddIfNotNull(file);
}
byte roundId = inc.ReadByte();
string campaignErrorInfo = string.Empty;
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
{
if (roundId != campaign.RoundID)
{
DebugConsole.AddWarning($"Received a StartGameFinalize message for an incorrect round (client: {campaign.RoundID}, server: {roundId}). The server might have started a new round before the client finished loading the previous one.");
requestNewRoundStart = true;
return;
}
campaignErrorInfo = $" Round start save ID: {debugStartGameCampaignSaveID}, last save id: {campaign.LastSaveID}, pending save id: {campaign.PendingSaveID}.";
}
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
@@ -1409,6 +1433,8 @@ namespace Barotrauma.Networking
private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc)
{
DebugConsole.Log($"Running {nameof(StartGame)} coroutine");
Character?.Remove();
Character = null;
HasSpawned = false;
@@ -1444,6 +1470,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!");
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1461,7 +1488,11 @@ namespace Barotrauma.Networking
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameMain.LightManager.LightingEnabled = true;
#if DEBUG
GameMain.LightManager.LightingEnabled = !GameMain.DevMode;
#endif
ServerSettings.ReadMonsterEnabled(inc);
@@ -1500,6 +1531,7 @@ namespace Barotrauma.Networking
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList))
{
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
@@ -1515,6 +1547,7 @@ namespace Barotrauma.Networking
if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox))
{
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
@@ -1544,6 +1577,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
if (GameMain.NetLobbyScreen.SelectedShuttle == null ||
@@ -1556,6 +1590,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1568,14 +1603,15 @@ namespace Barotrauma.Networking
}
else
{
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign))
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign)
{
throw new InvalidOperationException("Attempted to start a campaign round when a campaign was not active.");
}
if (GameMain.GameSession?.CrewManager != null) { GameMain.GameSession.CrewManager.Reset(); }
GameMain.GameSession?.CrewManager?.Reset();
byte campaignID = inc.ReadByte();
byte roundID = inc.ReadByte();
UInt16 campaignSaveID = inc.ReadUInt16();
int nextLocationIndex = inc.ReadInt32();
int nextConnectionIndex = inc.ReadInt32();
@@ -1588,6 +1624,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to start campaign round (campaign ID does not match).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1604,6 +1641,7 @@ namespace Barotrauma.Networking
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("campaignsavetransfer.timeout"));
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
//use success status, even though this is a failure (no need to show a console error because we show it in the message box)
yield return CoroutineStatus.Success;
}
@@ -1617,6 +1655,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1630,12 +1669,18 @@ namespace Barotrauma.Networking
if (roundSummary != null)
{
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, () =>
{
DebugConsole.Log($"Set round ID from {campaign.RoundID} to {roundID}.");
campaign.RoundID = roundID;
});
roundSummary.ContinueButton.Visible = false;
}
else
{
GameMain.GameSession.StartRound(levelData, mirrorLevel, startOutpost: campaign?.GetPredefinedStartOutpost());
DebugConsole.Log($"Set round ID from {campaign.RoundID} to {roundID}.");
campaign.RoundID = roundID;
}
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
}
@@ -1654,9 +1699,16 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("There was an error initializing the round (disconnected during the StartGame coroutine.)");
roundInitStatus = RoundInitStatus.Error;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
if (requestNewRoundStart)
{
RequestNewRoundStart();
yield return CoroutineStatus.Success;
}
roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
//wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message
@@ -1678,6 +1730,12 @@ namespace Barotrauma.Networking
{
while (true)
{
if (requestNewRoundStart)
{
RequestNewRoundStart();
yield return CoroutineStatus.Success;
}
try
{
if (DateTime.Now > requestFinalizeTime)
@@ -1742,10 +1800,12 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError(roundInitStatus.ToString());
CoroutineManager.StartCoroutine(EndGame(""));
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
else
{
startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
}
@@ -1754,6 +1814,7 @@ namespace Barotrauma.Networking
GameMain.GameSession.Submarine.Info.IsFileCorrupted)
{
DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Info.Name}\".");
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1801,6 +1862,17 @@ namespace Barotrauma.Networking
AddChatMessage(message, ChatMessageType.Server);
yield return CoroutineStatus.Success;
void RequestNewRoundStart()
{
GameMain.GameSession?.EndRound("");
GameMain.NetLobbyScreen.Select();
CoroutineManager.StopCoroutines("LevelTransition");
roundInitStatus = RoundInitStatus.Error;
startGameCoroutine = null;
SendJoinOngoingRequest(joinButton: null);
requestNewRoundStart = false;
}
}
public IEnumerable<CoroutineStatus> EndGame(string endMessage, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults = null)
@@ -1910,6 +1982,7 @@ namespace Barotrauma.Networking
GameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
bool allowAFK = inc.ReadBoolean();
bool permadeathMode = inc.ReadBoolean();
bool ironmanMode = inc.ReadBoolean();
@@ -1929,7 +2002,7 @@ namespace Barotrauma.Networking
message = TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled");
}
new GUIMessageBox(TextManager.Get("PleaseWait"), message);
if (!(Screen.Selected is ModDownloadScreen)) { GameMain.NetLobbyScreen.Select(); }
if (Screen.Selected is not ModDownloadScreen) { GameMain.NetLobbyScreen.Select(); }
}
}
}
@@ -2002,8 +2075,7 @@ namespace Barotrauma.Networking
Name = tc.Name;
nameId = tc.NameId;
}
if (GameMain.NetLobbyScreen.CharacterNameBox != null &&
!GameMain.NetLobbyScreen.CharacterNameBox.Selected)
if (GameMain.NetLobbyScreen.CharacterNameBox is { Selected: false, Enabled: true })
{
GameMain.NetLobbyScreen.CharacterNameBox.Text = Name;
}
@@ -2117,6 +2189,7 @@ namespace Barotrauma.Networking
bool voiceChatEnabled = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
bool allowAFK = inc.ReadBoolean();
float traitorProbability = inc.ReadSingle();
int traitorDangerLevel = inc.ReadRangedInteger(TraitorEventPrefab.MinDangerLevel, TraitorEventPrefab.MaxDangerLevel);
@@ -2194,6 +2267,7 @@ namespace Barotrauma.Networking
}
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
GameMain.NetLobbyScreen.SetAllowAFK(allowAFK);
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount);
@@ -2386,6 +2460,7 @@ namespace Barotrauma.Networking
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteBoolean(GameMain.NetLobbyScreen.AFKSelected);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
@@ -2532,6 +2607,15 @@ namespace Barotrauma.Networking
msg.WriteUInt16(bot.ID);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void ToggleReserveBench(CharacterInfo bot, bool pendingHire = false)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.TOGGLE_RESERVE_BENCH);
msg.WriteUInt16(bot.ID);
msg.WriteBoolean(pendingHire);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void RequestFile(FileTransferType fileType, string file, string fileHash)
{
@@ -2796,8 +2880,12 @@ namespace Barotrauma.Networking
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{
msg.WriteBoolean(GameMain.NetLobbyScreen.Spectating);
msg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
bool writeInfo = characterInfo != null;
msg.WriteBoolean(writeInfo);
msg.WritePadBits();
if (characterInfo == null) { return; }
if (!writeInfo) { return; }
var head = characterInfo.Head;
@@ -3080,9 +3168,9 @@ namespace Barotrauma.Networking
}
/// <summary>
/// Tell the server to end the round (permission required)
/// Tell the server to end the round (permission required).
/// </summary>
public void RequestRoundEnd(bool save, bool quitCampaign = false)
public void RequestEndRound(bool save, bool quitCampaign = false)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
@@ -3094,7 +3182,31 @@ namespace Barotrauma.Networking
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
public bool JoinOnGoingClicked(GUIButton button, object _)
/// <summary>
/// End the round locally (just returning to the lobby without ending the round for everyone).
/// </summary>
public void EndRoundForSelf()
{
GameMain.GameSession?.EndRound(endMessage: string.Empty, createRoundSummary: false);
Submarine.Unload();
GameMain.NetLobbyScreen.Select();
Character.Controlled = null;
WaitForNextRoundRespawn = null;
RespawnManager = null;
EntityEventManager?.Clear();
LastSentEntityEventID = 0;
MyClient.CharacterID = Entity.NullEntityID;
roundInitStatus = RoundInitStatus.NotStarted;
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.ENDROUND_SELF);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
public bool SendJoinOngoingRequest(GUIButton joinButton)
{
MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
@@ -3106,23 +3218,32 @@ namespace Barotrauma.Networking
new GUIMessageBox("", TextManager.Get("campaignfiletransferinprogress"));
return false;
}
if (button != null) { button.Enabled = false; }
if (joinButton != null) { joinButton.Enabled = false; }
if (campaign != null) { LateCampaignJoin = true; }
if (ClientPeer == null) { return false; }
//assume we have the required sub files to start the round
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
SendStartGameResponse(readyToStart: true);
return false;
}
private void SendStartGameResponse(bool readyToStart)
{
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
//assume we have the required sub files to start the round
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
readyToStartMsg.WriteBoolean(true);
readyToStartMsg.WriteBoolean(readyToStart);
readyToStartMsg.WriteBoolean(GameMain.NetLobbyScreen.AFKSelected && ServerSettings.AllowAFK);
WriteCharacterInfo(readyToStartMsg);
ClientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable);
return false;
}
public bool SetReadyToStart(GUITickBox tickBox)