Unstable 1.8.4.0
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
@@ -28,11 +29,17 @@ namespace Barotrauma
|
||||
public XElement SaveMultiplayer(XElement parentElement)
|
||||
{
|
||||
var element = new XElement("bots", new XAttribute("hasbots", HasBots));
|
||||
foreach (CharacterInfo info in characterInfos)
|
||||
foreach (CharacterInfo info in GetCharacterInfos(includeReserveBench: true))
|
||||
{
|
||||
if (Level.Loaded != null)
|
||||
{
|
||||
if (!info.IsNewHire && (info.Character == null || info.Character.IsDead)) { continue; }
|
||||
//new hires and reserve benched CharacterInfos should be saved even though the Character doesn't exist
|
||||
if (!info.IsNewHire && !info.IsOnReserveBench)
|
||||
{
|
||||
//character being null either means the character has been removed, or that it hasn't spawn yet
|
||||
if (info.Character == null && !info.PendingSpawnToActiveService) { continue; }
|
||||
if (info.Character is { IsDead: true }) { continue; }
|
||||
}
|
||||
}
|
||||
|
||||
XElement characterElement = info.Save(element);
|
||||
@@ -63,5 +70,108 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadToggleReserveBenchMessage(IReadMessage inc, Client sender)
|
||||
{
|
||||
UInt16 botId = inc.ReadUInt16();
|
||||
bool pendingHire = inc.ReadBoolean();
|
||||
|
||||
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign mpCampaign) { return; }
|
||||
if (!CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
|
||||
{
|
||||
DebugConsole.NewMessage($"Client {sender.Name} is not allowed to modify the reserve bench status of bots (requires ManageHires)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingHire && mpCampaign.Map.CurrentLocation?.HireManager.PendingHires.FirstOrDefault(ci => ci.ID == botId) is CharacterInfo pendingCharacterInfo)
|
||||
{
|
||||
ToggleReserveBenchStatus(pendingCharacterInfo, sender, pendingHire: true);
|
||||
}
|
||||
else if (GameMain.GameSession.CrewManager?.GetCharacterInfos(includeReserveBench: true)?.FirstOrDefault(i => i.ID == botId) is CharacterInfo characterInfo)
|
||||
{
|
||||
ToggleReserveBenchStatus(characterInfo, sender);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to correctly handle (and document) transitions between the different possible statuses (BotStatus) bots might have
|
||||
/// relating to the reserve bench, assigning them the correct new status and into the right CrewManager lists.
|
||||
/// This will only take care of things relevant to the CrewManager (like maximum crew size), and will assume requirements
|
||||
/// to hiring (money, permissions) have already been handled.
|
||||
/// </summary>
|
||||
/// <param name="characterInfo">CharacterInfo of the bot</param>
|
||||
/// <param name="client">Which client requested changing the reserve bench status?</param>
|
||||
/// <param name="pendingHire">Is the bot a pending hire?</param>
|
||||
/// <param name="confirmPendingHire">Has the hire been confirmed now? This will store the bot in the CrewManager.</param>
|
||||
/// <param name="sendUpdate">By default, the method will trigger sending updated crew data to the clients, but this may not always be useful – eg. if this method is called as part of a longer procedure that will send the update in the end anyway.</param>
|
||||
public void ToggleReserveBenchStatus(CharacterInfo characterInfo, Client client, bool pendingHire = false, bool confirmPendingHire = false, bool sendUpdate = true)
|
||||
{
|
||||
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign mpCampaign) { return; }
|
||||
|
||||
if (confirmPendingHire && !pendingHire)
|
||||
{
|
||||
DebugConsole.ThrowError($"ToggleReserveBenchStatus: cannot confirm a hire that is not pending (bot {characterInfo.DisplayName})");
|
||||
}
|
||||
|
||||
BotStatus currentStatus = characterInfo.BotStatus;
|
||||
if (pendingHire && !confirmPendingHire)
|
||||
{
|
||||
if (!(mpCampaign.Map.CurrentLocation?.HireManager.PendingHires.Contains(characterInfo) ?? false))
|
||||
{
|
||||
DebugConsole.ThrowError($"ToggleReserveBenchStatus: bot {characterInfo.DisplayName} is supposed to be in the pending hires list, but can't be found there");
|
||||
}
|
||||
|
||||
if (currentStatus == BotStatus.PendingHireToActiveService)
|
||||
{
|
||||
characterInfo.BotStatus = BotStatus.PendingHireToReserveBench;
|
||||
GameServer.Log($"Client \"{client.Name}\" moved the pending hire \"{characterInfo.DisplayName}\" to the reserve bench.", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
else if (currentStatus == BotStatus.PendingHireToReserveBench)
|
||||
{
|
||||
if (GetCharacterInfos().Count() >= MaxCrewSize)
|
||||
{
|
||||
DebugConsole.NewMessage($"ToggleReserveBenchStatus: Tried moving pending hire {characterInfo.DisplayName} to active service, but MaxCrewSize has already been reached");
|
||||
return;
|
||||
}
|
||||
characterInfo.BotStatus = BotStatus.PendingHireToActiveService;
|
||||
GameServer.Log($"Client \"{client.Name}\" moved the pending hire \"{characterInfo.DisplayName}\" from the reserve bench to active service.", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
}
|
||||
else if (GetCharacterInfos(includeReserveBench: true).Contains(characterInfo) || confirmPendingHire)
|
||||
{
|
||||
if (currentStatus == BotStatus.ActiveService || (confirmPendingHire && currentStatus == BotStatus.PendingHireToReserveBench))
|
||||
{
|
||||
if (reserveBench.Contains(characterInfo))
|
||||
{
|
||||
DebugConsole.ThrowError($"ToggleReserveBenchStatus: Tried to add the same CharacterInfo ({characterInfo.DisplayName}) to reserve bench twice");
|
||||
}
|
||||
RemoveCharacterInfo(characterInfo);
|
||||
characterInfo.BotStatus = BotStatus.ReserveBench;
|
||||
GameServer.Log($"Client \"{client.Name}\" moved the bot \"{characterInfo.DisplayName}\" from active service to the reserve bench.", ServerLog.MessageType.ServerMessage);
|
||||
reserveBench.Add(characterInfo);
|
||||
}
|
||||
else if (currentStatus == BotStatus.ReserveBench || (confirmPendingHire && currentStatus == BotStatus.PendingHireToActiveService))
|
||||
{
|
||||
if (GetCharacterInfos().Count() >= MaxCrewSize)
|
||||
{
|
||||
DebugConsole.NewMessage($"ToggleReserveBenchStatus: Tried moving {characterInfo.DisplayName} to active service, but MaxCrewSize has already been reached");
|
||||
return;
|
||||
}
|
||||
RemoveCharacterInfo(characterInfo);
|
||||
characterInfo.BotStatus = BotStatus.ActiveService;
|
||||
GameServer.Log($"Client \"{client.Name}\" moved the bot \"{characterInfo.DisplayName}\" from the reserve bench to active service.", ServerLog.MessageType.ServerMessage);
|
||||
AddCharacterInfo(characterInfo);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"ToggleReserveBenchStatus: bot {characterInfo.DisplayName} not found from CrewManager");
|
||||
}
|
||||
|
||||
if (sendUpdate)
|
||||
{
|
||||
mpCampaign.SendCrewState();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-1
@@ -15,6 +15,12 @@ namespace Barotrauma
|
||||
#endif
|
||||
|
||||
public bool HasSpawned;
|
||||
|
||||
/// <summary>
|
||||
/// Respawning via shuttle has been blocked from permanently dead characters, but it should be possible when the player
|
||||
/// chooses a bot from the reserve bench and shuttles are enabled in the campaign.
|
||||
/// </summary>
|
||||
public bool ChosenNewBotViaShuttle;
|
||||
|
||||
public bool HasItemData
|
||||
{
|
||||
@@ -77,6 +83,7 @@ namespace Barotrauma
|
||||
string accountIdStr = element.GetAttributeString("accountid", null)
|
||||
?? element.GetAttributeString("steamid", "");
|
||||
AccountId = Networking.AccountId.Parse(accountIdStr);
|
||||
ChosenNewBotViaShuttle = element.GetAttributeBool("waitingforshuttle", false);
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
@@ -143,6 +150,7 @@ namespace Barotrauma
|
||||
{
|
||||
Reset();
|
||||
CharacterInfo.PermanentlyDead = true;
|
||||
GameMain.GameSession?.IncrementPermadeath(AccountId);
|
||||
DebugConsole.NewMessage($"Permadeath applied on {Name}'s CharacterCampaignData.CharacterInfo.");
|
||||
}
|
||||
|
||||
@@ -179,7 +187,8 @@ namespace Barotrauma
|
||||
XElement element = new XElement("CharacterCampaignData",
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("address", ClientAddress),
|
||||
new XAttribute("accountid", AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : ""));
|
||||
new XAttribute("accountid", AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : ""),
|
||||
new XAttribute("waitingforshuttle", ChosenNewBotViaShuttle));
|
||||
CharacterInfo?.Save(element);
|
||||
if (itemData != null) { element.Add(itemData); }
|
||||
if (healthData != null) { element.Add(healthData); }
|
||||
|
||||
+64
-24
@@ -121,21 +121,21 @@ namespace Barotrauma
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(savePath)) { return; }
|
||||
|
||||
GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), savePath, GameModePreset.MultiPlayerCampaign, startingSettings, seed);
|
||||
GameMain.GameSession = new GameSession(new SubmarineInfo(subPath), Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.MultiPlayerCampaign, startingSettings, seed);
|
||||
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
SaveUtil.SaveGame(GameMain.GameSession.DataPath);
|
||||
|
||||
DebugConsole.NewMessage("Campaign started!", Color.Cyan);
|
||||
DebugConsole.NewMessage("Current location: " + GameMain.GameSession.Map.CurrentLocation.DisplayName, Color.Cyan);
|
||||
((MultiPlayerCampaign)GameMain.GameSession.GameMode).LoadInitialLevel();
|
||||
}
|
||||
|
||||
public static void LoadCampaign(string selectedSave, Client client)
|
||||
public static void LoadCampaign(CampaignDataPath path, Client client)
|
||||
{
|
||||
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
|
||||
try
|
||||
{
|
||||
SaveUtil.LoadGame(selectedSave);
|
||||
SaveUtil.LoadGame(path);
|
||||
if (GameMain.GameSession.GameMode is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
mpCampaign.LastSaveID++;
|
||||
@@ -148,7 +148,7 @@ namespace Barotrauma
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string errorMsg = $"Error while loading the save {selectedSave}";
|
||||
string errorMsg = $"Error while loading the save {path.LoadPath}";
|
||||
if (client != null)
|
||||
{
|
||||
GameMain.Server?.SendDirectChatMessage($"{errorMsg}: {e.Message}\n{e.StackTrace}", client, ChatMessageType.Error);
|
||||
@@ -209,7 +209,7 @@ namespace Barotrauma
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadCampaign(saveFiles[saveIndex].FilePath, client: null);
|
||||
LoadCampaign(CampaignDataPath.CreateRegular(saveFiles[saveIndex].FilePath), client: null);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -243,6 +243,12 @@ namespace Barotrauma
|
||||
savedExperiencePoints.RemoveAll(s => client.AccountId == s.AccountId || client.Connection.Endpoint.Address == s.Address);
|
||||
}
|
||||
|
||||
public void RefreshCharacterCampaignData(Character character, bool refreshHealthData)
|
||||
{
|
||||
var matchingData = characterData.FirstOrDefault(c => c.CharacterInfo == character.Info);
|
||||
matchingData?.Refresh(character, refreshHealthData: refreshHealthData);
|
||||
}
|
||||
|
||||
public void SavePlayers()
|
||||
{
|
||||
//refresh the character data of clients who are still in the server
|
||||
@@ -289,7 +295,8 @@ namespace Barotrauma
|
||||
data.Refresh(character, refreshHealthData: character.CauseOfDeath?.Type != CauseOfDeathType.Disconnected);
|
||||
characterData.Add(data);
|
||||
}
|
||||
else
|
||||
//check the cause of death in the CharacterInfo too (the character instance may have despawned, so we can't just rely on that)
|
||||
else if (data.CharacterInfo.CauseOfDeath is not { Type: CauseOfDeathType.Disconnected })
|
||||
{
|
||||
//character dead or removed -> reduce skills, remove items, health data, etc
|
||||
data.CharacterInfo.ApplyDeathEffects();
|
||||
@@ -389,7 +396,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
// Event history must be registered before ending the round or it will be cleared
|
||||
GameMain.GameSession.EventManager.RegisterEventHistory();
|
||||
GameMain.GameSession.EventManager.StoreEventDataAtRoundEnd();
|
||||
}
|
||||
|
||||
//store the currently active missions at this point so we can communicate their states to clients, they're cleared in EndRound
|
||||
@@ -406,13 +413,13 @@ namespace Barotrauma
|
||||
LeaveUnconnectedSubs(leavingSub);
|
||||
NextLevel = newLevel;
|
||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||
SaveUtil.SaveGame(GameMain.GameSession.DataPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
PendingSubmarineSwitch = null;
|
||||
GameMain.Server.EndGame(TransitionType.None, wasSaved: false);
|
||||
LoadCampaign(GameMain.GameSession.SavePath, client: null);
|
||||
LoadCampaign(GameMain.GameSession.DataPath, client: null);
|
||||
LastSaveID++;
|
||||
IncrementAllLastUpdateIds();
|
||||
yield return CoroutineStatus.Success;
|
||||
@@ -638,6 +645,7 @@ namespace Barotrauma
|
||||
|
||||
msg.WriteBoolean(IsFirstRound);
|
||||
msg.WriteByte(CampaignID);
|
||||
msg.WriteByte(RoundID);
|
||||
msg.WriteUInt16(lastSaveID);
|
||||
msg.WriteString(map.Seed);
|
||||
|
||||
@@ -1208,18 +1216,22 @@ namespace Barotrauma
|
||||
public void ServerReadCrew(IReadMessage msg, Client sender)
|
||||
{
|
||||
UInt16[] pendingHires = null;
|
||||
bool[] pendingToReserveBench = null;
|
||||
Dictionary<int, BotStatus> existingBotsClient = null;
|
||||
|
||||
bool updatePending = msg.ReadBoolean();
|
||||
if (updatePending)
|
||||
{
|
||||
ushort pendingHireLength = msg.ReadUInt16();
|
||||
pendingHires = new UInt16[pendingHireLength];
|
||||
pendingToReserveBench = new bool[pendingHireLength];
|
||||
for (int i = 0; i < pendingHireLength; i++)
|
||||
{
|
||||
pendingHires[i] = msg.ReadUInt16();
|
||||
pendingToReserveBench[i] = msg.ReadBoolean();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool validateHires = msg.ReadBoolean();
|
||||
|
||||
bool renameCharacter = msg.ReadBoolean();
|
||||
@@ -1229,9 +1241,9 @@ namespace Barotrauma
|
||||
if (renameCharacter)
|
||||
{
|
||||
renamedIdentifier = msg.ReadUInt16();
|
||||
newName = msg.ReadString();
|
||||
newName = Client.SanitizeName(msg.ReadString());
|
||||
existingCrewMember = msg.ReadBoolean();
|
||||
if (!GameMain.Server.IsNameValid(sender, newName))
|
||||
if (!GameMain.Server.IsNameValid(sender, newName, clientRenamingSelf: renamedIdentifier == sender.CharacterInfo?.ID))
|
||||
{
|
||||
renameCharacter = false;
|
||||
}
|
||||
@@ -1249,7 +1261,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (fireCharacter && AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
|
||||
{
|
||||
firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == firedIdentifier);
|
||||
firedCharacter = CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(info => info.ID == firedIdentifier);
|
||||
if (firedCharacter != null && (firedCharacter.Character?.IsBot ?? true))
|
||||
{
|
||||
CrewManager.FireCharacter(firedCharacter);
|
||||
@@ -1267,7 +1279,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (existingCrewMember && CrewManager != null)
|
||||
{
|
||||
characterInfo = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == renamedIdentifier);
|
||||
characterInfo = CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(info => info.ID == renamedIdentifier);
|
||||
}
|
||||
else if (!existingCrewMember && location.HireManager != null)
|
||||
{
|
||||
@@ -1318,6 +1330,7 @@ namespace Barotrauma
|
||||
if (updatePending)
|
||||
{
|
||||
List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>();
|
||||
int i = 0;
|
||||
foreach (UInt16 identifier in pendingHires)
|
||||
{
|
||||
CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.ID == identifier);
|
||||
@@ -1326,9 +1339,11 @@ namespace Barotrauma
|
||||
DebugConsole.ThrowError($"Tried to add a character that doesn't exist ({identifier}) to pending hires");
|
||||
continue;
|
||||
}
|
||||
|
||||
match.BotStatus = pendingToReserveBench[i++] ? BotStatus.PendingHireToReserveBench : BotStatus.PendingHireToActiveService;
|
||||
|
||||
pendingHireInfos.Add(match);
|
||||
if (pendingHireInfos.Count + CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
|
||||
if (pendingHireInfos.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -1396,14 +1411,21 @@ namespace Barotrauma
|
||||
foreach (CharacterInfo pendingHire in pendingHires)
|
||||
{
|
||||
msg.WriteUInt16(pendingHire.ID);
|
||||
msg.WriteBoolean(pendingHire.BotStatus == BotStatus.PendingHireToReserveBench);
|
||||
}
|
||||
|
||||
var hiredCharacters = CrewManager.GetCharacterInfos().Where(ci => ci.IsNewHire);
|
||||
msg.WriteUInt16((ushort)hiredCharacters.Count());
|
||||
foreach (CharacterInfo info in hiredCharacters)
|
||||
var crewManager = CrewManager.GetCharacterInfos();
|
||||
msg.WriteUInt16((ushort)crewManager.Count());
|
||||
foreach (CharacterInfo info in crewManager)
|
||||
{
|
||||
info.ServerWrite(msg);
|
||||
}
|
||||
|
||||
var reserveBench = CrewManager.GetReserveBenchInfos();
|
||||
msg.WriteUInt16((ushort)reserveBench.Count());
|
||||
foreach (CharacterInfo info in reserveBench)
|
||||
{
|
||||
info.ServerWrite(msg);
|
||||
msg.WriteInt32(info.Salary);
|
||||
}
|
||||
|
||||
bool validRenaming = renamedCrewMember.id > 0 && !string.IsNullOrEmpty(renamedCrewMember.newName);
|
||||
@@ -1466,7 +1488,16 @@ namespace Barotrauma
|
||||
return wallet.Balance + Bank.Balance;
|
||||
}
|
||||
|
||||
public override void Save(XElement element)
|
||||
/// <summary>
|
||||
/// Serializes the campaign and character data to XML.
|
||||
/// </summary>
|
||||
/// <param name="element">Game session element to save the campaign data to.</param>
|
||||
/// <param name="isSavingOnLoading">
|
||||
/// Whether the save is being done during loading to ensure the campaign ID matches the one in the save file.
|
||||
/// Used to work around some quirks with the backup save system.
|
||||
/// See: <see cref="SaveUtil.SaveGame(CampaignDataPath,bool)"/>
|
||||
/// </param>
|
||||
public override void Save(XElement element, bool isSavingOnLoading)
|
||||
{
|
||||
element.Add(new XAttribute("campaignid", CampaignID));
|
||||
XElement modeElement = new XElement("MultiPlayerCampaign",
|
||||
@@ -1516,8 +1547,16 @@ namespace Barotrauma
|
||||
|
||||
element.Add(modeElement);
|
||||
|
||||
//save character data to a separate file
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
// save character data to a separate file
|
||||
|
||||
// When loading a campaign in multiplayer, we save the campaign to ensure the campaign ID that gets assigned
|
||||
// matches the one in the save file, this is a problem with the backup save system since this causes the
|
||||
// character data to save too, and we don't want to overwrite the main save file's character data.
|
||||
// So we instead save over the load path in this case, which in backup saves is the backup file
|
||||
// which we don't mind getting overriden since the data should be the same
|
||||
string characterDataPath = isSavingOnLoading
|
||||
? GetCharacterDataPathForLoading()
|
||||
: GetCharacterDataPathForSaving();
|
||||
XDocument characterDataDoc = new XDocument(new XElement("CharacterData"));
|
||||
foreach (CharacterCampaignData cd in characterData)
|
||||
{
|
||||
@@ -1525,6 +1564,7 @@ namespace Barotrauma
|
||||
}
|
||||
try
|
||||
{
|
||||
SaveUtil.DeleteIfExists(characterDataPath);
|
||||
characterDataDoc.SaveSafe(characterDataPath);
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -1544,7 +1584,7 @@ namespace Barotrauma
|
||||
/// eg. when using this method to save a character itself restored from the backup.</param>
|
||||
public void SaveSingleCharacter(CharacterCampaignData newData, bool skipBackup = false)
|
||||
{
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
string characterDataPath = GetCharacterDataPathForSaving();
|
||||
if (!File.Exists(characterDataPath))
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to load the character data for the campaign. Could not find the file \"{characterDataPath}\".");
|
||||
|
||||
Reference in New Issue
Block a user