Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop
This commit is contained in:
@@ -57,6 +57,8 @@ namespace Barotrauma
|
||||
msg.WriteString(Name);
|
||||
msg.WriteString(OriginalName);
|
||||
msg.WriteBoolean(RenamingEnabled);
|
||||
msg.WriteByte((byte)BotStatus);
|
||||
msg.WriteInt32(Salary);
|
||||
msg.WriteByte((byte)Head.Preset.TagSet.Count);
|
||||
foreach (Identifier tag in Head.Preset.TagSet)
|
||||
{
|
||||
|
||||
@@ -85,7 +85,8 @@ namespace Barotrauma
|
||||
|
||||
partial void UpdateNetInput()
|
||||
{
|
||||
if (!(this is AICharacter) || IsRemotePlayer)
|
||||
//non-ai character (a character that was previously controlled by a player) or a remote player (which can be an AI character controlled by a player)
|
||||
if (this is not AICharacter || IsRemotePlayer)
|
||||
{
|
||||
if (!CanMove)
|
||||
{
|
||||
@@ -447,12 +448,7 @@ namespace Barotrauma
|
||||
if (!fixedRotation)
|
||||
{
|
||||
tempBuffer.WriteSingle(AnimController.Collider.Rotation);
|
||||
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
|
||||
AnimController.Collider.AngularVelocity =
|
||||
AnimController.Collider.PhysEnabled ?
|
||||
0.0f :
|
||||
NetConfig.Quantize(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel, 8);
|
||||
tempBuffer.WriteRangedSingle(MathHelper.Clamp(AnimController.Collider.AngularVelocity, -MaxAngularVel, MaxAngularVel), -MaxAngularVel, MaxAngularVel, 8);
|
||||
tempBuffer.WriteSingle(AnimController.Collider.AngularVelocity);
|
||||
}
|
||||
|
||||
|
||||
@@ -497,6 +493,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case CharacterStatusEventData statusEventData:
|
||||
WriteStatus(msg, statusEventData.ForceAfflictionData);
|
||||
msg.WriteBoolean(GodMode);
|
||||
break;
|
||||
case UpdateSkillsEventData updateSkillsData:
|
||||
if (Info?.Job is { } job)
|
||||
@@ -532,7 +529,14 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case AssignCampaignInteractionEventData _:
|
||||
msg.WriteByte((byte)CampaignInteractionType);
|
||||
|
||||
bool canClientInteract = true;
|
||||
if (CampaignInteractionType == CampaignMode.InteractionType.Talk &&
|
||||
ActiveConversation != null)
|
||||
{
|
||||
canClientInteract = ActiveConversation.CanClientStartConversation(c);
|
||||
}
|
||||
msg.WriteByte((byte)(canClientInteract ? CampaignInteractionType : CampaignMode.InteractionType.None));
|
||||
msg.WriteBoolean(RequireConsciousnessForCustomInteract);
|
||||
break;
|
||||
case ObjectiveManagerStateEventData objectiveManagerStateEventData:
|
||||
@@ -683,6 +687,7 @@ namespace Barotrauma
|
||||
{
|
||||
CharacterHealth.ServerWrite(msg);
|
||||
}
|
||||
|
||||
if (AnimController?.LimbJoints == null)
|
||||
{
|
||||
//0 limbs severed
|
||||
@@ -738,7 +743,7 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||
Client ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this && (!c.SpectateOnly || !GameMain.Server.ServerSettings.AllowSpectating));
|
||||
if (ownerClient != null)
|
||||
{
|
||||
msg.WriteBoolean(true);
|
||||
|
||||
@@ -378,11 +378,11 @@ namespace Barotrauma
|
||||
AssignOnExecute("killdisconnectedtimer", (string[] args) =>
|
||||
{
|
||||
if (args.Length < 1 || GameMain.Server == null) return;
|
||||
float seconds = GameMain.Server.ServerSettings.KillDisconnectedTime;
|
||||
if (float.TryParse(args[0], out seconds))
|
||||
if (float.TryParse(args[0], out float seconds))
|
||||
{
|
||||
seconds = Math.Max(0, seconds);
|
||||
NewMessage("Set kill disconnected timer to " + ToolBox.SecondsToReadableTime(seconds), Color.White);
|
||||
GameMain.Server.ServerSettings.KillDisconnectedTime = seconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -391,13 +391,13 @@ namespace Barotrauma
|
||||
});
|
||||
AssignOnClientRequestExecute("killdisconnectedtimer", (Client client, Vector2 cursorPos, string[] args) =>
|
||||
{
|
||||
if (args.Length < 1 || GameMain.Server == null) return;
|
||||
float seconds = GameMain.Server.ServerSettings.KillDisconnectedTime;
|
||||
if (float.TryParse(args[0], out seconds))
|
||||
if (args.Length < 1 || GameMain.Server == null) { return; }
|
||||
if (float.TryParse(args[0], out float seconds))
|
||||
{
|
||||
seconds = Math.Max(0, seconds);
|
||||
GameMain.Server.SendConsoleMessage("Set kill disconnected timer to " + ToolBox.SecondsToReadableTime(seconds).Value, client);
|
||||
NewMessage(client.Name + " set kill disconnected timer to " + ToolBox.SecondsToReadableTime(seconds), Color.White);
|
||||
GameMain.Server.ServerSettings.KillDisconnectedTime = seconds;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -499,14 +499,10 @@ namespace Barotrauma
|
||||
NewMessage(GameMain.Server.ServerSettings.StartWhenClientsReady ? "Enabled starting the round automatically when clients are ready." : "Disabled starting the round automatically when clients are ready.", Color.White);
|
||||
});
|
||||
|
||||
AssignOnExecute("spawn|spawncharacter", (string[] args) =>
|
||||
{
|
||||
SpawnCharacter(args, Vector2.Zero, out string errorMsg);
|
||||
if (!string.IsNullOrWhiteSpace(errorMsg))
|
||||
{
|
||||
ThrowError(errorMsg);
|
||||
}
|
||||
});
|
||||
AssignOnExecute("spawn|spawncharacter", args => SpawnCharacter(args, Vector2.Zero));
|
||||
AssignOnExecute("spawnnpc", args => SpawnCharacter(args, Vector2.Zero, true));
|
||||
AssignOnClientRequestExecute("spawn|spawncharacter", (Client client, Vector2 cursorPos, string[] args) => SpawnCharacter(args, cursorPos));
|
||||
AssignOnClientRequestExecute("spawnnpc", (Client client, Vector2 cursorPos, string[] args) => SpawnCharacter(args, cursorPos, true));
|
||||
|
||||
AssignOnExecute("giveperm", (string[] args) =>
|
||||
{
|
||||
@@ -1647,18 +1643,6 @@ namespace Barotrauma
|
||||
});
|
||||
#endif
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"spawn|spawncharacter",
|
||||
(Client client, Vector2 cursorPos, string[] args) =>
|
||||
{
|
||||
SpawnCharacter(args, cursorPos, out string errorMsg);
|
||||
if (!string.IsNullOrWhiteSpace(errorMsg))
|
||||
{
|
||||
ThrowError(errorMsg);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"banaddress|banip",
|
||||
(Client client, Vector2 cursorPos, string[] args) =>
|
||||
@@ -1830,11 +1814,17 @@ namespace Barotrauma
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
|
||||
{
|
||||
//cursor doesn't exist server-side, use to the position of the sub instead
|
||||
TeleportCharacter(cursorWorldPos: Submarine.MainSub?.WorldPosition ?? Vector2.Zero, Character.Controlled, args);
|
||||
});
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"teleportcharacter|teleport",
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
TeleportCharacter(cursorWorldPos, client.Character, args);
|
||||
TeleportCharacter(cursorWorldPos, client.Character, args);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1892,14 +1882,16 @@ namespace Barotrauma
|
||||
"godmode",
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
Character targetCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false);
|
||||
|
||||
if (targetCharacter == null) { return; }
|
||||
|
||||
targetCharacter.GodMode = !targetCharacter.GodMode;
|
||||
|
||||
NewMessage(targetCharacter.Name + (targetCharacter.GodMode ? "'s godmode turned on by \"" : "'s godmode turned off by \"") + client.Name + "\"", Color.White);
|
||||
GameMain.Server.SendConsoleMessage(targetCharacter.Name + (targetCharacter.GodMode ? "'s godmode on" : "'s godmode off"), client);
|
||||
bool? godmodeStateOnFirstCharacter = null;
|
||||
HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode, client);
|
||||
void ToggleGodMode(Character targetCharacter)
|
||||
{
|
||||
targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode;
|
||||
godmodeStateOnFirstCharacter = targetCharacter.GodMode;
|
||||
GameMain.NetworkMember.CreateEntityEvent(targetCharacter, new Character.CharacterStatusEventData());
|
||||
NewMessage(targetCharacter.Name + (targetCharacter.GodMode ? "'s godmode turned on by \"" : "'s godmode turned off by \"") + client.Name + "\"", Color.White);
|
||||
GameMain.Server.SendConsoleMessage(targetCharacter.Name + (targetCharacter.GodMode ? "'s godmode on" : "'s godmode off"), client);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1965,7 +1957,7 @@ namespace Barotrauma
|
||||
bool healAll = args.Length > 0 && args[0].Equals("all", StringComparison.OrdinalIgnoreCase);
|
||||
if (client.Character != null)
|
||||
{
|
||||
HealCharacter(client.Character, healAll);
|
||||
HealCharacter(client.Character, healAll, client);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1975,11 +1967,7 @@ namespace Barotrauma
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
bool healAll = args.Length > 1 && args[1].Equals("all", StringComparison.OrdinalIgnoreCase);
|
||||
Character healedCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(healAll ? args.Take(args.Length - 1).ToArray() : args);
|
||||
if (healedCharacter != null)
|
||||
{
|
||||
HealCharacter(healedCharacter, healAll);
|
||||
}
|
||||
HandleCommandForCrewOrSingleCharacter(args, (Character targetCharacter) => HealCharacter(targetCharacter, healAll, client), client);
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -17,9 +17,15 @@ namespace Barotrauma
|
||||
|
||||
private static readonly Dictionary<Client, ConversationAction> lastActiveAction = new Dictionary<Client, ConversationAction>();
|
||||
|
||||
/// <summary>
|
||||
/// Clients who this Conversation prompt is being currently shown to
|
||||
/// </summary>
|
||||
private readonly HashSet<Client> targetClients = new HashSet<Client>();
|
||||
private readonly Dictionary<Client, DateTime> ignoredClients = new Dictionary<Client, DateTime>();
|
||||
|
||||
/// <summary>
|
||||
/// Clients who this Conversation prompt is being currently shown to
|
||||
/// </summary>
|
||||
public IEnumerable<Client> TargetClients
|
||||
{
|
||||
get
|
||||
@@ -52,10 +58,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanClientStartConversation(Client client)
|
||||
{
|
||||
if (!TargetTag.IsEmpty)
|
||||
{
|
||||
var targets = ParentEvent.GetTargets(TargetTag).Where(e => IsValidTarget(e));
|
||||
return targets.Contains(client.Character);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void IgnoreClient(Client c, float seconds)
|
||||
{
|
||||
if (!ignoredClients.ContainsKey(c)) { ignoredClients.Add(c, DateTime.Now); }
|
||||
ignoredClients[c] = DateTime.Now + TimeSpan.FromSeconds(seconds);
|
||||
//this action is not active for the client if they decided to ignore it
|
||||
if (lastActiveAction.TryGetValue(c, out ConversationAction lastActive) && lastActive == this)
|
||||
{
|
||||
lastActiveAction.Remove(c);
|
||||
}
|
||||
Reset();
|
||||
}
|
||||
|
||||
@@ -116,13 +137,14 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (c.InGame && c.Character != null)
|
||||
if (CanClientReceive(c))
|
||||
{
|
||||
if (targetCharacter == null || targetCharacter == c.Character)
|
||||
{
|
||||
targetClients.Add(c);
|
||||
lastActiveAction[c] = this;
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
DebugConsole.Log($"Sending conversationaction {ParentEvent.Prefab.Identifier} to client...");
|
||||
ServerWrite(speaker, c, interrupt);
|
||||
}
|
||||
}
|
||||
@@ -130,6 +152,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is it possible for the client to receive ConversationActions
|
||||
/// (just checking if they're in game, controlling a character and not marked as ignoring the action,
|
||||
/// but not accounting for whether this action targets them or not).
|
||||
/// </summary>
|
||||
/// <param name="c"></param>
|
||||
/// <returns></returns>
|
||||
private bool CanClientReceive(Client c)
|
||||
{
|
||||
return c != null && c.InGame && c.Character != null && !ignoredClients.ContainsKey(c);
|
||||
}
|
||||
|
||||
public void ServerWrite(Character speaker, Client client, bool interrupt)
|
||||
{
|
||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
@@ -26,8 +27,11 @@ namespace Barotrauma
|
||||
|
||||
public void ServerRead(IReadMessage inc, Client sender)
|
||||
{
|
||||
const float IgnoreTime = 3f;
|
||||
|
||||
UInt16 actionId = inc.ReadUInt16();
|
||||
byte selectedOption = inc.ReadByte();
|
||||
bool isIgnore = selectedOption == byte.MaxValue;
|
||||
|
||||
foreach (Event ev in activeEvents)
|
||||
{
|
||||
@@ -40,24 +44,38 @@ namespace Barotrauma
|
||||
if (!convAction.TargetClients.Contains(sender))
|
||||
{
|
||||
#if DEBUG || UNSTABLE
|
||||
DebugConsole.ThrowError($"Client \"{sender.Name}\" tried to respond to a ConversationAction that was not targeted to them ({convAction.Text}).");
|
||||
if (!isIgnore)
|
||||
{
|
||||
DebugConsole.ThrowError($"Client \"{sender.Name}\" tried to respond to a ConversationAction that was not targeted to them ({convAction.Text}).");
|
||||
}
|
||||
#endif
|
||||
convAction.IgnoreClient(sender, IgnoreTime);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (convAction.SelectedOption > -1)
|
||||
{
|
||||
//someone else already chose an option for this conversation: interrupt for this client
|
||||
DebugConsole.Log($"Client replied to {ev.Prefab.Identifier}, but option already selected for conversation, interrupt for the client");
|
||||
convAction.ServerWrite(convAction.Speaker, sender, interrupt: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (selectedOption == byte.MaxValue)
|
||||
if (isIgnore)
|
||||
{
|
||||
convAction.IgnoreClient(sender, 3f);
|
||||
DebugConsole.NewMessage($"Client ignored ConversationAction (event {ev.Prefab.Identifier}).");
|
||||
convAction.IgnoreClient(sender, IgnoreTime);
|
||||
//no more target clients (the only/last target ignored the conversation action)
|
||||
// -> reset the action so it can appear when some client becomes available
|
||||
if (convAction.TargetClients.None())
|
||||
{
|
||||
DebugConsole.NewMessage($"No target clients for event {ev.Prefab.Identifier}, retrying in " + (IgnoreTime + 1.0f));
|
||||
convAction.RetriggerAfter(IgnoreTime + 1.0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage($"Client selected option {selectedOption} for ConversationAction in event {ev.Prefab.Identifier}.");
|
||||
convAction.SelectedOption = selectedOption;
|
||||
if (convAction.Options.Any() && !convAction.GetEndingOptions().Contains(selectedOption))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -24,7 +22,7 @@ namespace Barotrauma
|
||||
character.WriteSpawnData(msg, character.ID, restrictMessageSize: false);
|
||||
msg.WriteBoolean(requireKill.Contains(character));
|
||||
msg.WriteBoolean(requireRescue.Contains(character));
|
||||
msg.WriteUInt16((ushort)characterItems[character].Count());
|
||||
msg.WriteUInt16((ushort)characterItems[character].Count);
|
||||
foreach (Item item in characterItems[character])
|
||||
{
|
||||
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1);
|
||||
|
||||
@@ -29,15 +29,26 @@ namespace Barotrauma
|
||||
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
||||
{
|
||||
base.ServerWriteInitial(msg, c);
|
||||
|
||||
msg.WriteByte((byte)characters.Count);
|
||||
foreach (Character character in characters)
|
||||
{
|
||||
character.WriteSpawnData(msg, character.ID, restrictMessageSize: false);
|
||||
var items = characterItems[character];
|
||||
msg.WriteUInt16((ushort)items.Count);
|
||||
foreach (Item item in items)
|
||||
{
|
||||
item.WriteSpawnData(msg, item.ID, item.ParentInventory?.Owner?.ID ?? Entity.NullEntityID, 0, item.ParentInventory?.FindIndex(item) ?? -1);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
bool targetFound = spawnInfo.ContainsKey(target) && target.Item != null;
|
||||
bool targetFound = spawnInfo.TryGetValue(target, out SpawnInfo sInfo) && target.Item != null;
|
||||
msg.WriteBoolean(targetFound);
|
||||
if (!targetFound) { continue; }
|
||||
|
||||
msg.WriteBoolean(spawnInfo[target].UsedExistingItem);
|
||||
if (spawnInfo[target].UsedExistingItem)
|
||||
msg.WriteBoolean(sInfo.UsedExistingItem);
|
||||
if (sInfo.UsedExistingItem)
|
||||
{
|
||||
msg.WriteUInt16(target.Item.ID);
|
||||
}
|
||||
@@ -45,14 +56,14 @@ namespace Barotrauma
|
||||
{
|
||||
target.Item.WriteSpawnData(msg,
|
||||
target.Item.ID,
|
||||
spawnInfo[target].OriginalInventoryID,
|
||||
spawnInfo[target].OriginalItemContainerIndex,
|
||||
spawnInfo[target].OriginalSlotIndex);
|
||||
sInfo.OriginalInventoryID,
|
||||
sInfo.OriginalItemContainerIndex,
|
||||
sInfo.OriginalSlotIndex);
|
||||
msg.WriteUInt16(target.ParentTarget?.Item?.ID ?? Entity.NullEntityID);
|
||||
}
|
||||
|
||||
msg.WriteByte((byte)spawnInfo[target].ExecutedEffectIndices.Count);
|
||||
foreach ((int listIndex, int effectIndex) in spawnInfo[target].ExecutedEffectIndices)
|
||||
msg.WriteByte((byte)sInfo.ExecutedEffectIndices.Count);
|
||||
foreach ((int listIndex, int effectIndex) in sInfo.ExecutedEffectIndices)
|
||||
{
|
||||
msg.WriteByte((byte)listIndex);
|
||||
msg.WriteByte((byte)effectIndex);
|
||||
@@ -64,9 +75,9 @@ namespace Barotrauma
|
||||
{
|
||||
base.ServerWrite(msg);
|
||||
msg.WriteByte((byte)targets.Count);
|
||||
for (int i = 0; i < targets.Count; i++)
|
||||
foreach (Target t in targets)
|
||||
{
|
||||
msg.WriteByte((byte)targets[i].State);
|
||||
msg.WriteByte((byte)t.State);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,9 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
name = doc.Root.GetAttributeString(nameof(ServerSettings.Name), "Server");
|
||||
name = doc.Root.GetAttributeString(nameof(ServerSettings.ServerName),
|
||||
//backwards compatibility
|
||||
doc.Root.GetAttributeString("name", "Server"));
|
||||
port = doc.Root.GetAttributeInt(nameof(ServerSettings.Port), NetConfig.DefaultPort);
|
||||
queryPort = doc.Root.GetAttributeInt(nameof(ServerSettings.QueryPort), NetConfig.DefaultQueryPort);
|
||||
publiclyVisible = doc.Root.GetAttributeBool(nameof(ServerSettings.IsPublic), false);
|
||||
@@ -289,6 +291,12 @@ namespace Barotrauma
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
case "-multiclienttestmode":
|
||||
#if DEBUG
|
||||
CharacterCampaignData.RequireClientNameMatch = true;
|
||||
#endif
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-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())
|
||||
{
|
||||
@@ -180,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); }
|
||||
|
||||
+35
-13
@@ -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
|
||||
@@ -390,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
|
||||
@@ -639,6 +645,7 @@ namespace Barotrauma
|
||||
|
||||
msg.WriteBoolean(IsFirstRound);
|
||||
msg.WriteByte(CampaignID);
|
||||
msg.WriteByte(RoundID);
|
||||
msg.WriteUInt16(lastSaveID);
|
||||
msg.WriteString(map.Seed);
|
||||
|
||||
@@ -1209,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();
|
||||
@@ -1232,7 +1243,7 @@ namespace Barotrauma
|
||||
renamedIdentifier = msg.ReadUInt16();
|
||||
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;
|
||||
}
|
||||
@@ -1250,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);
|
||||
@@ -1268,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)
|
||||
{
|
||||
@@ -1319,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);
|
||||
@@ -1327,12 +1339,15 @@ 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;
|
||||
if (match.BotStatus == BotStatus.PendingHireToActiveService)
|
||||
{
|
||||
//can't add more bots to active service is max has been reached
|
||||
if (pendingHireInfos.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize) { continue; }
|
||||
}
|
||||
|
||||
pendingHireInfos.Add(match);
|
||||
if (pendingHireInfos.Count + CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
location.HireManager.PendingHires = pendingHireInfos;
|
||||
}
|
||||
@@ -1397,14 +1412,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);
|
||||
|
||||
@@ -14,11 +14,15 @@ namespace Barotrauma.Items.Components
|
||||
DockingButtonClicked = dockingButtonClicked;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: an enumeration would be much cleaner
|
||||
public bool MaintainPos;
|
||||
public bool LevelStartSelected;
|
||||
public bool LevelEndSelected;
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
|
||||
public bool MaintainPos { get; set; }
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
|
||||
public bool LevelStartSelected { get; set; }
|
||||
|
||||
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
|
||||
public bool LevelEndSelected { get; set; }
|
||||
|
||||
public bool UnsentChanges
|
||||
{
|
||||
|
||||
@@ -6,7 +6,15 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger(Channel, MinChannel, MaxChannel);
|
||||
SharedEventWrite(msg);
|
||||
}
|
||||
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
{
|
||||
SharedEventRead(msg);
|
||||
|
||||
// Create an event to notify other clients about the changes
|
||||
item.CreateServerEvent(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,7 +195,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void BanPlayer(string name, Either<Address, AccountId> addressOrAccountId, string reason, TimeSpan? duration)
|
||||
{
|
||||
if (addressOrAccountId.TryGet(out Address address) && address.IsLocalHost) { return; }
|
||||
if (addressOrAccountId.TryGet(out Address address) && address.IsLocalHost)
|
||||
{
|
||||
DebugConsole.AddWarning($"Cannot ban localhost ({address.StringRepresentation})");
|
||||
return;
|
||||
}
|
||||
|
||||
var existingBan = bannedPlayers.Find(bp => bp.AddressOrAccountId == addressOrAccountId);
|
||||
if (existingBan != null) { bannedPlayers.Remove(existingBan); }
|
||||
|
||||
@@ -84,7 +84,11 @@ namespace Barotrauma.Networking
|
||||
set
|
||||
{
|
||||
if (characterInfo == value) { return; }
|
||||
characterInfo?.Remove();
|
||||
if (characterInfo is { Character: null })
|
||||
{
|
||||
//if a character hasn't spawned for this characterInfo, we can remove the info and free the sprites and such
|
||||
characterInfo.Remove();
|
||||
}
|
||||
characterInfo = value;
|
||||
}
|
||||
}
|
||||
@@ -94,6 +98,7 @@ namespace Barotrauma.Networking
|
||||
public NetworkConnection Connection { get; set; }
|
||||
|
||||
public bool SpectateOnly;
|
||||
public bool AFK;
|
||||
public bool? WaitForNextRoundRespawn;
|
||||
|
||||
public int KarmaKickCount;
|
||||
@@ -335,10 +340,21 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
//the bot has spawned, but the new CharacterCampaignData technically hasn't, because we just created it
|
||||
characterData.HasSpawned = true;
|
||||
mpCampaign.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.CharacterInfo);
|
||||
}
|
||||
|
||||
SpectateOnly = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResetSync()
|
||||
{
|
||||
NeedsMidRoundSync = false;
|
||||
PendingPositionUpdates.Clear();
|
||||
EntityEventLastSent.Clear();
|
||||
LastSentEntityEventID = 0;
|
||||
LastRecvEntityEventID = 0;
|
||||
UnreceivedEntityEventCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,7 +223,14 @@ namespace Barotrauma.Networking
|
||||
serverPeer = new LidgrenServerPeer(ownerKey, ServerSettings, callbacks);
|
||||
if (registerToServerList)
|
||||
{
|
||||
registeredToSteamMaster = SteamManager.CreateServer(this, ServerSettings.IsPublic);
|
||||
try
|
||||
{
|
||||
registeredToSteamMaster = SteamManager.CreateServer(this, ServerSettings.IsPublic);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.NewMessage($"Steam registering skipped due to error (and probably more of it was printed above): {e.Message}");
|
||||
}
|
||||
Eos.EosSessionManager.UpdateOwnedSession(Option.None, ServerSettings);
|
||||
}
|
||||
}
|
||||
@@ -423,12 +430,15 @@ namespace Barotrauma.Networking
|
||||
for (int i = Character.CharacterList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
Character character = Character.CharacterList[i];
|
||||
if (!character.ClientDisconnected) { continue; }
|
||||
|
||||
Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && character.IsClientOwner(c));
|
||||
bool spectating = owner is { SpectateOnly: true } && ServerSettings.AllowSpectating;
|
||||
|
||||
if (!character.ClientDisconnected && !spectating) { continue; }
|
||||
|
||||
bool canOwnerTakeControl =
|
||||
owner != null && owner.InGame && !owner.NeedsMidRoundSync &&
|
||||
(!ServerSettings.AllowSpectating || !owner.SpectateOnly ||
|
||||
(!spectating ||
|
||||
(permadeathMode && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected)));
|
||||
if (!character.IsDead)
|
||||
{
|
||||
@@ -438,8 +448,17 @@ namespace Barotrauma.Networking
|
||||
character.SetStun(1.0f);
|
||||
}
|
||||
|
||||
float killTime = permadeathMode ? ServerSettings.DespawnDisconnectedPermadeathTime : ServerSettings.KillDisconnectedTime;
|
||||
//owner decided to spectate -> kill the character immediately,
|
||||
//it's no longer needed and should not be considered the character this client is controlling
|
||||
//the client can still regain control, because the character can be revived in the block below if the client rejoins as a non-spectator
|
||||
if (spectating)
|
||||
{
|
||||
killTime = 0.0f;
|
||||
}
|
||||
|
||||
if ((OwnerConnection == null || owner?.Connection != OwnerConnection) &&
|
||||
character.KillDisconnectedTimer > (permadeathMode ? ServerSettings.DespawnDisconnectedPermadeathTime : ServerSettings.KillDisconnectedTime))
|
||||
character.KillDisconnectedTimer > killTime)
|
||||
{
|
||||
character.Kill(CauseOfDeathType.Disconnected, null);
|
||||
continue;
|
||||
@@ -618,8 +637,9 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else if (ServerSettings.StartWhenClientsReady)
|
||||
{
|
||||
int clientsReady = connectedClients.Count(c => c.GetVote<bool>(VoteType.StartRound));
|
||||
if (clientsReady / (float)connectedClients.Count >= ServerSettings.StartWhenClientsReadyRatio)
|
||||
var startVoteEligibleClients = connectedClients.Where(c => Voting.CanVoteToStartRound(c));
|
||||
int clientsReady = startVoteEligibleClients.Count(c => c.GetVote<bool>(VoteType.StartRound));
|
||||
if (clientsReady / (float)startVoteEligibleClients.Count() >= ServerSettings.StartWhenClientsReadyRatio)
|
||||
{
|
||||
readyToStartAutomatically = true;
|
||||
}
|
||||
@@ -649,7 +669,8 @@ namespace Barotrauma.Networking
|
||||
c.ChatSpamSpeed = Math.Max(0.0f, c.ChatSpamSpeed - deltaTime);
|
||||
|
||||
//constantly increase AFK timer if the client is controlling a character (gets reset to zero every time an input is received)
|
||||
if (GameStarted && c.Character != null && !c.Character.IsDead && !c.Character.IsIncapacitated)
|
||||
if (GameStarted && c.Character != null && !c.Character.IsDead && !c.Character.IsIncapacitated &&
|
||||
(!c.AFK || !ServerSettings.AllowAFK))
|
||||
{
|
||||
if (c.Connection != OwnerConnection && c.Permissions != ClientPermissions.All) { c.KickAFKTimer += deltaTime; }
|
||||
}
|
||||
@@ -835,6 +856,7 @@ namespace Barotrauma.Networking
|
||||
if (connectedClient != null)
|
||||
{
|
||||
connectedClient.ReadyToStart = inc.ReadBoolean();
|
||||
connectedClient.AFK = inc.ReadBoolean();
|
||||
UpdateCharacterInfo(inc, connectedClient);
|
||||
|
||||
//game already started -> send start message immediately
|
||||
@@ -970,6 +992,10 @@ namespace Barotrauma.Networking
|
||||
case ClientPacketHeader.SERVER_COMMAND:
|
||||
ClientReadServerCommand(inc);
|
||||
break;
|
||||
case ClientPacketHeader.ENDROUND_SELF:
|
||||
connectedClient.InGame = false;
|
||||
connectedClient.ResetSync();
|
||||
break;
|
||||
case ClientPacketHeader.CREW:
|
||||
ReadCrewMessage(inc, connectedClient);
|
||||
break;
|
||||
@@ -997,6 +1023,9 @@ namespace Barotrauma.Networking
|
||||
case ClientPacketHeader.TAKEOVERBOT:
|
||||
ReadTakeOverBotMessage(inc, connectedClient);
|
||||
break;
|
||||
case ClientPacketHeader.TOGGLE_RESERVE_BENCH:
|
||||
GameMain.GameSession?.CrewManager?.ReadToggleReserveBenchMessage(inc, connectedClient);
|
||||
break;
|
||||
case ClientPacketHeader.FILE_REQUEST:
|
||||
if (ServerSettings.AllowFileTransfers)
|
||||
{
|
||||
@@ -1233,6 +1262,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
c.LastRecvChatMsgID = NetIdUtils.Clamp(inc.ReadUInt16(), c.LastRecvChatMsgID, c.LastChatMsgQueueID);
|
||||
c.LastRecvClientListUpdate = NetIdUtils.Clamp(inc.ReadUInt16(), c.LastRecvClientListUpdate, LastClientListUpdateID);
|
||||
c.AFK = inc.ReadBoolean();
|
||||
|
||||
ReadClientNameChange(c, inc);
|
||||
|
||||
@@ -1298,6 +1328,7 @@ namespace Barotrauma.Networking
|
||||
//check if midround syncing is needed due to missed unique events
|
||||
if (!midroundSyncingDone) { entityEventManager.InitClientMidRoundSync(c); }
|
||||
MissionAction.NotifyMissionsUnlockedThisRound(c);
|
||||
UnlockPathAction.NotifyPathsUnlockedThisRound(c);
|
||||
|
||||
if (GameMain.GameSession.GameMode is PvPMode)
|
||||
{
|
||||
@@ -1316,6 +1347,7 @@ namespace Barotrauma.Networking
|
||||
c.TeamID = CharacterTeamType.Team1;
|
||||
}
|
||||
c.InGame = true;
|
||||
c.AFK = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1558,17 +1590,23 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
CharacterInfo botInfo = GameMain.GameSession.CrewManager?.GetCharacterInfos()?.FirstOrDefault(i => i.ID == botId);
|
||||
CharacterInfo botInfo = GameMain.GameSession.CrewManager?.GetCharacterInfos(includeReserveBench: true)?.FirstOrDefault(i => i.ID == botId);
|
||||
|
||||
if (botInfo is { IsNewHire: true, Character: null })
|
||||
if (botInfo is { Character: null } && (botInfo.IsNewHire || botInfo.IsOnReserveBench))
|
||||
{
|
||||
SpawnAndTakeOverBot(campaign, botInfo, sender);
|
||||
if (IsUsingRespawnShuttle())
|
||||
{
|
||||
SpawnAndTakeOverBotInShuttle(campaign, botInfo, sender);
|
||||
}
|
||||
else
|
||||
{
|
||||
SpawnAndTakeOverBot(campaign, botInfo, sender);
|
||||
}
|
||||
}
|
||||
else if (botInfo?.Character == null || !botInfo.Character.IsBot)
|
||||
{
|
||||
SendConsoleMessage($"Could not find a bot with the id {botId}.", sender, Color.Red);
|
||||
DebugConsole.ThrowError($"Client {sender.Name} failed to take over a bot (Could not find a bot with the id {botId}).");
|
||||
return;
|
||||
}
|
||||
else if (ServerSettings.AllowBotTakeoverOnPermadeath)
|
||||
{
|
||||
@@ -1584,8 +1622,22 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static void SpawnAndTakeOverBot(CampaignMode campaign, CharacterInfo botInfo, Client client)
|
||||
{
|
||||
var mainSubSpawnpoint = WayPoint.SelectCrewSpawnPoints(botInfo.ToEnumerable().ToList(), Submarine.MainSub).FirstOrDefault();
|
||||
var spawnWaypoint = campaign.CrewManager.GetOutpostSpawnpoints()?.FirstOrDefault() ?? mainSubSpawnpoint;
|
||||
WayPoint mainSubSpawnpoint = WayPoint.SelectCrewSpawnPoints(botInfo.ToEnumerable().ToList(), Submarine.MainSub).FirstOrDefault();
|
||||
WayPoint outpostWaypoint = campaign.CrewManager.GetOutpostSpawnpoints()?.FirstOrDefault();
|
||||
WayPoint spawnWaypoint;
|
||||
|
||||
//give the bot the same salary the player had
|
||||
TransferPreviousSalaryToBot(campaign, botInfo, client);
|
||||
|
||||
if (botInfo.IsOnReserveBench)
|
||||
{
|
||||
spawnWaypoint = mainSubSpawnpoint ?? outpostWaypoint;
|
||||
}
|
||||
else
|
||||
{
|
||||
spawnWaypoint = outpostWaypoint ?? mainSubSpawnpoint;
|
||||
}
|
||||
|
||||
if (spawnWaypoint == null)
|
||||
{
|
||||
DebugConsole.ThrowError("SpawnAndTakeOverBot: Unable to find any spawn waypoints inside the sub");
|
||||
@@ -1598,13 +1650,59 @@ namespace Barotrauma.Networking
|
||||
DebugConsole.ThrowError("SpawnAndTakeOverBot: newCharacter is null somehow");
|
||||
return;
|
||||
}
|
||||
// No longer show the hired character in the HR list of current hires
|
||||
campaign.CrewManager.RemoveCharacterInfo(botInfo);
|
||||
|
||||
if (botInfo.IsOnReserveBench)
|
||||
{
|
||||
campaign.CrewManager.ToggleReserveBenchStatus(botInfo, client);
|
||||
}
|
||||
|
||||
newCharacter.TeamID = CharacterTeamType.Team1;
|
||||
campaign.CrewManager.InitializeCharacter(newCharacter, mainSubSpawnpoint, spawnWaypoint);
|
||||
client.TryTakeOverBot(newCharacter);
|
||||
Log($"Client \"{client.Name}\" took over the bot \"{botInfo.DisplayName}\".", ServerLog.MessageType.ServerMessage);
|
||||
});
|
||||
}
|
||||
|
||||
private static void SpawnAndTakeOverBotInShuttle(CampaignMode campaign, CharacterInfo botInfo, Client client)
|
||||
{
|
||||
if (botInfo.IsOnReserveBench && campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
//give the bot the same salary the player had
|
||||
TransferPreviousSalaryToBot(campaign, botInfo, client);
|
||||
|
||||
// Bring the bot from the reserve bench to active service
|
||||
mpCampaign.CrewManager.ToggleReserveBenchStatus(botInfo, client);
|
||||
Debug.Assert(botInfo.BotStatus == BotStatus.ActiveService);
|
||||
|
||||
Log($"Client \"{client.Name}\" chose to spawn as the bot \"{botInfo.DisplayName}\" in the next respawn shuttle.", ServerLog.MessageType.ServerMessage);
|
||||
|
||||
// Note: The following does what ServerSource/Networking/Client.cs:TryTakeOverBot() would do, but here we have
|
||||
// to do it without a Character (before the Character has spawned), to get them on the respawn shuttle
|
||||
|
||||
// Now that the old permanently killed character will be replaced, we can fully discard it
|
||||
mpCampaign.DiscardClientCharacterData(client);
|
||||
|
||||
client.CharacterInfo = botInfo;
|
||||
client.CharacterInfo.RenamingEnabled = true; // Grant one opportunity to rename a taken over bot
|
||||
client.CharacterInfo.IsNewHire = false;
|
||||
client.SpectateOnly = false;
|
||||
client.WaitForNextRoundRespawn = false; // =respawn asap
|
||||
|
||||
// Generate a new, less dead CharacterCampaignData for the client
|
||||
if (mpCampaign.SetClientCharacterData(client) is CharacterCampaignData characterData)
|
||||
{
|
||||
//the bot has spawned, but the new CharacterCampaignData technically hasn't, because we just created it
|
||||
characterData.HasSpawned = true;
|
||||
characterData.ChosenNewBotViaShuttle = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TransferPreviousSalaryToBot(CampaignMode campaign, CharacterInfo botInfo, Client client)
|
||||
{
|
||||
//give the bot the same salary the player had
|
||||
botInfo.LastRewardDistribution = Option<int>.Some(client?.Character?.Wallet.RewardDistribution ?? campaign.Bank.RewardDistribution);
|
||||
}
|
||||
|
||||
private void ClientReadServerCommand(IReadMessage inc)
|
||||
{
|
||||
@@ -1956,6 +2054,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
outmsg.WriteBoolean(GameStarted);
|
||||
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
|
||||
outmsg.WriteBoolean(ServerSettings.AllowAFK);
|
||||
outmsg.WriteBoolean(ServerSettings.RespawnMode == RespawnMode.Permadeath);
|
||||
outmsg.WriteBoolean(ServerSettings.IronmanMode);
|
||||
|
||||
@@ -2288,6 +2387,7 @@ namespace Barotrauma.Networking
|
||||
outmsg.WriteBoolean(ServerSettings.VoiceChatEnabled);
|
||||
|
||||
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
|
||||
outmsg.WriteBoolean(ServerSettings.AllowAFK);
|
||||
|
||||
outmsg.WriteSingle(ServerSettings.TraitorProbability);
|
||||
outmsg.WriteRangedInteger(ServerSettings.TraitorDangerLevel, TraitorEventPrefab.MinDangerLevel, TraitorEventPrefab.MaxDangerLevel);
|
||||
@@ -2671,7 +2771,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
//give the clients a few seconds to request missing sub/shuttle files before starting the round
|
||||
float waitForResponseTimer = 5.0f;
|
||||
while (connectedClients.Any(c => !c.ReadyToStart) && waitForResponseTimer > 0.0f)
|
||||
while (connectedClients.Any(c => !c.ReadyToStart && !c.AFK) && waitForResponseTimer > 0.0f)
|
||||
{
|
||||
waitForResponseTimer -= CoroutineManager.DeltaTime;
|
||||
yield return CoroutineStatus.Running;
|
||||
@@ -2780,7 +2880,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
yield return CoroutineStatus.Failure;
|
||||
}
|
||||
|
||||
campaign.RoundID++;
|
||||
SendStartMessage(roundStartSeed, campaign.NextLevel.Seed, GameMain.GameSession, connectedClients, includesFinalize: false);
|
||||
GameMain.GameSession.StartRound(campaign.NextLevel, startOutpost: campaign.GetPredefinedStartOutpost(), mirrorLevel: campaign.MirrorLevel);
|
||||
SubmarineSwitchLoad = false;
|
||||
@@ -2821,6 +2921,7 @@ namespace Barotrauma.Networking
|
||||
campaign.CargoManager.CreatePurchasedItems();
|
||||
//midround-joining clients need to be informed of pending/new hires at outposts
|
||||
if (isOutpost) { campaign.SendCrewState(); }
|
||||
//campaign.SendCrewState(); // pending/new hires, reserve bench
|
||||
}
|
||||
|
||||
if (GameMain.GameSession.Missions.None(m => !m.Prefab.AllowOutpostNPCs))
|
||||
@@ -2881,13 +2982,7 @@ namespace Barotrauma.Networking
|
||||
List<CharacterInfo> characterInfos = new List<CharacterInfo>();
|
||||
foreach (Client client in teamClients)
|
||||
{
|
||||
client.NeedsMidRoundSync = false;
|
||||
|
||||
client.PendingPositionUpdates.Clear();
|
||||
client.EntityEventLastSent.Clear();
|
||||
client.LastSentEntityEventID = 0;
|
||||
client.LastRecvEntityEventID = 0;
|
||||
client.UnreceivedEntityEventCount = 0;
|
||||
client.ResetSync();
|
||||
|
||||
if (client.CharacterInfo == null)
|
||||
{
|
||||
@@ -2928,19 +3023,11 @@ namespace Barotrauma.Networking
|
||||
hadBots = false;
|
||||
}
|
||||
|
||||
List<WayPoint> spawnWaypoints = null;
|
||||
List<WayPoint> mainSubWaypoints = teamSub != null ? WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]).ToList() : null;
|
||||
WayPoint[] spawnWaypoints = null;
|
||||
WayPoint[] mainSubWaypoints = teamSub != null ? WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSubs[n]) : null;
|
||||
if (Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost())
|
||||
{
|
||||
spawnWaypoints = WayPoint.GetOutpostSpawnPoints(teamID);
|
||||
while (spawnWaypoints.Count > characterInfos.Count)
|
||||
{
|
||||
spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count));
|
||||
}
|
||||
while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count)
|
||||
{
|
||||
spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]);
|
||||
}
|
||||
spawnWaypoints = WayPoint.SelectOutpostSpawnPoints(characterInfos, teamID);
|
||||
}
|
||||
if (teamSub != null)
|
||||
{
|
||||
@@ -2948,9 +3035,10 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
spawnWaypoints = mainSubWaypoints;
|
||||
}
|
||||
Debug.Assert(spawnWaypoints.Count == mainSubWaypoints.Count);
|
||||
Debug.Assert(spawnWaypoints.Length == mainSubWaypoints.Length);
|
||||
}
|
||||
|
||||
|
||||
// Spawn players
|
||||
for (int i = 0; i < teamClients.Count; i++)
|
||||
{
|
||||
//if there's a main sub waypoint available (= the spawnpoint the character would've spawned at, if they'd spawned in the main sub instead of the outpost),
|
||||
@@ -3003,7 +3091,8 @@ namespace Barotrauma.Networking
|
||||
spawnedCharacter.SetOwnerClient(teamClients[i]);
|
||||
AddCharacterToList(teamID, spawnedCharacter);
|
||||
}
|
||||
|
||||
|
||||
// Spawn bots
|
||||
for (int i = teamClients.Count; i < teamClients.Count + bots.Count; i++)
|
||||
{
|
||||
WayPoint jobItemSpawnPoint = mainSubWaypoints != null ? mainSubWaypoints[i] : spawnWaypoints[i];
|
||||
@@ -3167,6 +3256,7 @@ namespace Barotrauma.Networking
|
||||
int nextLocationIndex = campaign.Map.Locations.FindIndex(l => l.LevelData == campaign.NextLevel);
|
||||
int nextConnectionIndex = campaign.Map.Connections.FindIndex(c => c.LevelData == campaign.NextLevel);
|
||||
msg.WriteByte(campaign.CampaignID);
|
||||
msg.WriteByte(campaign == null ? (byte)0 : campaign.RoundID);
|
||||
msg.WriteUInt16(campaign.LastSaveID);
|
||||
msg.WriteInt32(nextLocationIndex);
|
||||
msg.WriteInt32(nextConnectionIndex);
|
||||
@@ -3225,6 +3315,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
msg.WriteString(contentFile.Path.Value);
|
||||
}
|
||||
msg.WriteByte((GameMain.GameSession.Campaign as MultiPlayerCampaign)?.RoundID ?? 0);
|
||||
msg.WriteInt32(Submarine.MainSub?.Info.EqualityCheckVal ?? 0);
|
||||
msg.WriteByte((byte)GameMain.GameSession.Missions.Count());
|
||||
foreach (Mission mission in GameMain.GameSession.Missions)
|
||||
@@ -3293,9 +3384,7 @@ namespace Barotrauma.Networking
|
||||
entityEventManager.Clear();
|
||||
foreach (Client c in connectedClients)
|
||||
{
|
||||
c.EntityEventLastSent.Clear();
|
||||
c.PendingPositionUpdates.Clear();
|
||||
c.PositionUpdateLastSent.Clear();
|
||||
c.ResetSync();
|
||||
}
|
||||
|
||||
if (GameStarted)
|
||||
@@ -3418,13 +3507,13 @@ namespace Barotrauma.Networking
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
return TryChangeClientName(c, newName);
|
||||
return TryChangeClientName(c, newName, clientRenamingSelf: true);
|
||||
}
|
||||
|
||||
public bool TryChangeClientName(Client c, string newName)
|
||||
public bool TryChangeClientName(Client c, string newName, bool clientRenamingSelf = false)
|
||||
{
|
||||
newName = Client.SanitizeName(newName);
|
||||
if (newName != c.Name && !string.IsNullOrEmpty(newName) && IsNameValid(c, newName))
|
||||
if (newName != c.Name && !string.IsNullOrEmpty(newName) && IsNameValid(c, newName, clientRenamingSelf))
|
||||
{
|
||||
c.LastNameChangeTime = DateTime.Now;
|
||||
string oldName = c.Name;
|
||||
@@ -3443,7 +3532,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNameValid(Client c, string newName)
|
||||
public bool IsNameValid(Client c, string newName, bool clientRenamingSelf = false)
|
||||
{
|
||||
if (c.Connection != OwnerConnection)
|
||||
{
|
||||
@@ -3465,18 +3554,23 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
Client nameTakenByClient = ConnectedClients.Find(c2 => c != c2 && Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
|
||||
Client nameTakenByClient = ConnectedClients.Find(c2 =>
|
||||
!(clientRenamingSelf && c == c2) && // only allow renaming one's own client with a similar name
|
||||
Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
|
||||
if (nameTakenByClient != null)
|
||||
{
|
||||
SendDirectChatMessage($"ServerMessage.NameChangeFailedClientTooSimilar~[newname]={newName}~[takenname]={nameTakenByClient.Name}", c, ChatMessageType.ServerMessageBox);
|
||||
return false;
|
||||
}
|
||||
|
||||
Character nameTakenByCharacter =
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).FirstOrDefault(c2 => c2 != c.Character && Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
|
||||
if (nameTakenByCharacter != null)
|
||||
|
||||
string existingTooSimilarName = GameMain.GameSession?.CrewManager?
|
||||
.GetCharacterInfos(includeReserveBench: true)
|
||||
.FirstOrDefault(ci =>
|
||||
(!clientRenamingSelf || ci.ID != c.Character?.ID) &&
|
||||
Homoglyphs.Compare(ci.Name.ToLower(), newName.ToLower()))?.Name;
|
||||
if (!existingTooSimilarName.IsNullOrEmpty())
|
||||
{
|
||||
SendDirectChatMessage($"ServerMessage.NameChangeFailedClientTooSimilar~[newname]={newName}~[takenname]={nameTakenByCharacter.Name}", c, ChatMessageType.ServerMessageBox);
|
||||
SendDirectChatMessage($"ServerMessage.NameChangeFailedTooSimilar~[newname]={newName}~[takenname]={existingTooSimilarName}", c, ChatMessageType.ServerMessageBox);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -4029,10 +4123,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
SendVoteStatus(connectedClients);
|
||||
|
||||
int endVoteCount = ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound));
|
||||
int endVoteMax = GameMain.Server.ConnectedClients.Count(c => c.HasSpawned);
|
||||
var endVoteEligibleClients = connectedClients.Where(c => Voting.CanVoteToEndRound(c));
|
||||
int endVoteCount = endVoteEligibleClients.Count(c => c.GetVote<bool>(VoteType.EndRound));
|
||||
int endVoteMax = endVoteEligibleClients.Count();
|
||||
if (ServerSettings.AllowEndVoting && endVoteMax > 0 &&
|
||||
((float)endVoteCount / (float)endVoteMax) >= ServerSettings.EndVoteRequiredRatio)
|
||||
(endVoteCount / (float)endVoteMax) >= ServerSettings.EndVoteRequiredRatio)
|
||||
{
|
||||
Log("Ending round by votes (" + endVoteCount + "/" + (endVoteMax - endVoteCount) + ")", ServerLog.MessageType.ServerMessage);
|
||||
EndGame(wasSaved: false);
|
||||
@@ -4269,13 +4364,17 @@ namespace Barotrauma.Networking
|
||||
private void UpdateCharacterInfo(IReadMessage message, Client sender)
|
||||
{
|
||||
bool spectateOnly = message.ReadBoolean();
|
||||
bool characterDiscarded = message.ReadBoolean();
|
||||
bool readInfo = message.ReadBoolean();
|
||||
message.ReadPadBits();
|
||||
|
||||
sender.SpectateOnly = spectateOnly && (ServerSettings.AllowSpectating || sender.Connection == OwnerConnection);
|
||||
if (sender.SpectateOnly) { return; }
|
||||
|
||||
if (!readInfo) { return; }
|
||||
|
||||
var netInfo = INetSerializableStruct.Read<NetCharacterInfo>(message);
|
||||
|
||||
if (sender.SpectateOnly) { return; }
|
||||
if (charInfoRateLimiter.IsLimitReached(sender)) { return; }
|
||||
|
||||
string newName = netInfo.NewName;
|
||||
@@ -4286,7 +4385,7 @@ namespace Barotrauma.Networking
|
||||
else
|
||||
{
|
||||
newName = Client.SanitizeName(newName);
|
||||
if (!IsNameValid(sender, newName))
|
||||
if (!IsNameValid(sender, newName, clientRenamingSelf: true))
|
||||
{
|
||||
newName = sender.Name;
|
||||
}
|
||||
@@ -4297,11 +4396,16 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
// If a CharacterInfo for this Client already exists on the server, make sure it is used, and prevent the Client from replacing it
|
||||
var existingCampaignData = (GameMain.GameSession?.Campaign as MultiPlayerCampaign)?.GetClientCharacterData(sender);
|
||||
if (existingCampaignData != null)
|
||||
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
sender.CharacterInfo = existingCampaignData.CharacterInfo;
|
||||
return;
|
||||
if (characterDiscarded) { mpCampaign.DiscardClientCharacterData(sender); }
|
||||
var existingCampaignData = mpCampaign.GetClientCharacterData(sender);
|
||||
if (existingCampaignData != null)
|
||||
{
|
||||
DebugConsole.NewMessage("Client attempted to modify their CharacterInfo, but they already have an existing campaign character. Ignoring the modifications.");
|
||||
sender.CharacterInfo = existingCampaignData.CharacterInfo;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, newName);
|
||||
@@ -4687,7 +4791,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
private List<Client> GetPlayingClients()
|
||||
{
|
||||
List<Client> playingClients = new List<Client>(connectedClients);
|
||||
List<Client> playingClients = new List<Client>(connectedClients.Where(c => !c.AFK || !ServerSettings.AllowAFK));
|
||||
if (ServerSettings.AllowSpectating)
|
||||
{
|
||||
playingClients.RemoveAll(static c => c.SpectateOnly);
|
||||
|
||||
+20
-2
@@ -210,11 +210,11 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
PendingClient? pendingClient = pendingClients.Find(c => c.Connection.NetConnection == inc.SenderConnection);
|
||||
|
||||
if (pendingClient is null)
|
||||
{
|
||||
pendingClient = new PendingClient(new LidgrenConnection(inc.SenderConnection));
|
||||
pendingClients.Add(pendingClient);
|
||||
GameServer.Log($"Incoming connection from {pendingClient.Connection.NetConnection?.RemoteEndPoint?.ToString() ?? "null"}.", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
|
||||
inc.SenderConnection.Approve();
|
||||
@@ -228,7 +228,25 @@ namespace Barotrauma.Networking
|
||||
|
||||
IReadMessage inc = lidgrenMsg.ToReadMessage();
|
||||
|
||||
var (_, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
PeerPacketHeaders peerPacketHeaders = default;
|
||||
try
|
||||
{
|
||||
peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (pendingClient != null)
|
||||
{
|
||||
//pending (= not yet authenticated) client sent malformed data, immediately ban them so they can't use this for spamming
|
||||
GameServer.Log($"Received an invalid connection attempt from {pendingClient.Connection.NetConnection?.RemoteEndPoint?.ToString() ?? "null"}. Banning the IP.", ServerLog.MessageType.DoSProtection);
|
||||
serverSettings.BanList.BanPlayer(name: "Unknown", endpoint: pendingClient.Connection.Endpoint, reason: "Invalid connection attempt", duration: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
var (_, packetHeader, initialization) = peerPacketHeaders;
|
||||
|
||||
if (packetHeader.IsConnectionInitializationStep() && pendingClient != null && initialization.HasValue)
|
||||
{
|
||||
|
||||
+5
-5
@@ -41,7 +41,7 @@ namespace Barotrauma.Networking
|
||||
public ConnectionInitialization InitializationStep;
|
||||
public double UpdateTime;
|
||||
public double TimeOut;
|
||||
public int Retries;
|
||||
public int PasswordRetries;
|
||||
public Int32? PasswordSalt;
|
||||
public bool AuthSessionStarted;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace Barotrauma.Networking
|
||||
OwnerKey = Option.None;
|
||||
Connection = conn;
|
||||
InitializationStep = ConnectionInitialization.AuthInfoAndVersion;
|
||||
Retries = 0;
|
||||
PasswordRetries = 0;
|
||||
PasswordSalt = null;
|
||||
UpdateTime = Timing.TotalTime + Timing.Step * 3.0;
|
||||
TimeOut = NetworkConnection.TimeoutThreshold;
|
||||
@@ -156,8 +156,8 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
pendingClient.Retries++;
|
||||
if (serverSettings.BanAfterWrongPassword && pendingClient.Retries > serverSettings.MaxPasswordRetriesBeforeBan)
|
||||
pendingClient.PasswordRetries++;
|
||||
if (serverSettings.BanAfterWrongPassword && pendingClient.PasswordRetries > serverSettings.MaxPasswordRetriesBeforeBan)
|
||||
{
|
||||
const string banMsg = "Failed to enter correct password too many times";
|
||||
BanPendingClient(pendingClient, banMsg, null);
|
||||
@@ -286,7 +286,7 @@ namespace Barotrauma.Networking
|
||||
structToSend = new ServerPeerPasswordPacket
|
||||
{
|
||||
Salt = GetSalt(pendingClient),
|
||||
RetriesLeft = Option<int>.Some(pendingClient.Retries)
|
||||
RetriesLeft = Option<int>.Some(pendingClient.PasswordRetries)
|
||||
};
|
||||
|
||||
static Option<int> GetSalt(PendingClient client)
|
||||
|
||||
@@ -38,8 +38,9 @@ namespace Barotrauma.Networking
|
||||
continue;
|
||||
}
|
||||
|
||||
// Respawning might also be needed in permadeath mode for disconnected characters, but never for permanently dead ones
|
||||
// Respawning can still happen in permadeath mode (disconnected characters, reserve bench...), but never for permanently dead ones
|
||||
if (GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath } &&
|
||||
matchingData is not { ChosenNewBotViaShuttle: true } && // respawning as a bot that should respawn the usual way via shuttle
|
||||
(matchingData?.CharacterInfo is { PermanentlyDead: true } || c.Character is { IsDead: true }))
|
||||
{
|
||||
continue;
|
||||
@@ -47,7 +48,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (campaign != null)
|
||||
{
|
||||
if (matchingData != null && matchingData.HasSpawned)
|
||||
if (matchingData != null && matchingData.HasSpawned && !matchingData.ChosenNewBotViaShuttle)
|
||||
{
|
||||
//in the campaign mode, wait for the client to choose whether they want to spawn
|
||||
if (!c.WaitForNextRoundRespawn.HasValue || c.WaitForNextRoundRespawn.Value) { continue; }
|
||||
@@ -66,7 +67,7 @@ namespace Barotrauma.Networking
|
||||
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { return false; }
|
||||
if (c.Character != null && !c.Character.IsDead) { return false; }
|
||||
|
||||
var matchingData = campaign.GetClientCharacterData(c);
|
||||
CharacterCampaignData matchingData = campaign.GetClientCharacterData(c);
|
||||
if (matchingData != null && matchingData.HasSpawned)
|
||||
{
|
||||
if (Character.CharacterList.Any(c =>
|
||||
@@ -83,6 +84,16 @@ namespace Barotrauma.Networking
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ClientHasChosenNewBotViaShuttle(Client c)
|
||||
{
|
||||
if (GameMain.GameSession.GameMode is MultiPlayerCampaign mpCampaign &&
|
||||
mpCampaign.GetClientCharacterData(c) is CharacterCampaignData matchingData)
|
||||
{
|
||||
return matchingData.ChosenNewBotViaShuttle;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<CharacterInfo> GetBotsToRespawn(CharacterTeamType teamId)
|
||||
{
|
||||
//this works under the assumption that GetCharacterInfos only returns bots in MP
|
||||
@@ -152,7 +163,8 @@ namespace Barotrauma.Networking
|
||||
|
||||
private static int GetMinCharactersToRespawn()
|
||||
{
|
||||
return Math.Max((int)(GameMain.Server.ConnectedClients.Count * GameMain.Server.ServerSettings.MinRespawnRatio), 1);
|
||||
int respawnableClientCount = GameMain.Server.ConnectedClients.Count(c => c.InGame && (!c.AFK || !GameMain.Server.ServerSettings.AllowAFK));
|
||||
return Math.Max((int)(respawnableClientCount * GameMain.Server.ServerSettings.MinRespawnRatio), 1);
|
||||
}
|
||||
|
||||
private bool ShouldStartRespawnCountdown(int characterToRespawnCount)
|
||||
@@ -237,6 +249,7 @@ namespace Barotrauma.Networking
|
||||
teamSpecificState.CurrentState = State.Transporting;
|
||||
}
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Dynamic);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -429,15 +442,10 @@ namespace Barotrauma.Networking
|
||||
ItemPrefab batteryPrefab = ItemPrefab.Find(null, "batterycell".ToIdentifier());
|
||||
|
||||
//the spawnpoints where the characters will spawn
|
||||
var selectedSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, respawnSub);
|
||||
if (isPvPMode && Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost())
|
||||
{
|
||||
var spawnWaypoints = WayPoint.GetOutpostSpawnPoints(teamID);
|
||||
for (int i = 0; i < characterInfos.Count; i++)
|
||||
{
|
||||
selectedSpawnPoints[i] = spawnWaypoints.GetRandomUnsynced();
|
||||
}
|
||||
}
|
||||
var selectedSpawnPoints =
|
||||
isPvPMode && Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost() ?
|
||||
WayPoint.SelectOutpostSpawnPoints(characterInfos, teamID) :
|
||||
WayPoint.SelectCrewSpawnPoints(characterInfos, respawnSub);
|
||||
|
||||
//the spawnpoints where they would spawn if they were spawned inside the main sub
|
||||
//(in order to give them appropriate ID card tags)
|
||||
@@ -459,7 +467,7 @@ namespace Barotrauma.Networking
|
||||
//when the character spawns, set the client's name to match
|
||||
if (clients[i].PendingName == characterInfo.Name)
|
||||
{
|
||||
GameMain.Server?.TryChangeClientName(clients[i], clients[i].PendingName);
|
||||
GameMain.Server?.TryChangeClientName(clients[i], clients[i].PendingName, clientRenamingSelf: true);
|
||||
clients[i].PendingName = null;
|
||||
}
|
||||
|
||||
@@ -678,6 +686,7 @@ namespace Barotrauma.Networking
|
||||
msg.WriteUInt16((ushort)teamSpecificState.PendingRespawnCount);
|
||||
msg.WriteUInt16((ushort)teamSpecificState.RequiredRespawnCount);
|
||||
msg.WriteBoolean(IsRespawnDecisionPendingForClient(c));
|
||||
msg.WriteBoolean(ClientHasChosenNewBotViaShuttle(c));
|
||||
msg.WriteBoolean(teamSpecificState.RespawnCountdownStarted);
|
||||
msg.WriteSingle((float)(teamSpecificState.RespawnTime - DateTime.Now).TotalSeconds);
|
||||
break;
|
||||
|
||||
@@ -268,7 +268,6 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
XDocument doc = new XDocument(new XElement("serversettings"));
|
||||
|
||||
doc.Root.SetAttributeValue("name", ServerName);
|
||||
doc.Root.SetAttributeValue("port", Port);
|
||||
|
||||
if (QueryPort != 0)
|
||||
@@ -280,8 +279,6 @@ namespace Barotrauma.Networking
|
||||
doc.Root.SetAttributeValue("enableupnp", EnableUPnP);
|
||||
doc.Root.SetAttributeValue("autorestart", autoRestart);
|
||||
|
||||
doc.Root.SetAttributeValue("ServerMessage", ServerMessageText);
|
||||
|
||||
doc.Root.SetAttributeValue("HiddenSubs", string.Join(",", HiddenSubs));
|
||||
|
||||
doc.Root.SetAttributeValue("AllowedRandomMissionTypes", string.Join(",", AllowedRandomMissionTypes));
|
||||
@@ -325,6 +322,11 @@ namespace Barotrauma.Networking
|
||||
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, doc.Root);
|
||||
|
||||
//backwards compatibility
|
||||
if (serverName.IsNullOrEmpty()) { ServerName = doc.Root.GetAttributeString("name", ""); }
|
||||
if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
if (ServerMessageText.IsNullOrEmpty()) { ServerMessageText = doc.Root.GetAttributeString("ServerMessage", ""); }
|
||||
|
||||
if (string.IsNullOrEmpty(doc.Root.GetAttributeString("losmode", "")))
|
||||
{
|
||||
LosMode = GameSettings.CurrentConfig.Graphics.LosMode;
|
||||
@@ -409,10 +411,6 @@ namespace Barotrauma.Networking
|
||||
AllowedRandomMissionTypes = doc.Root.GetAttributeIdentifierArray(
|
||||
"AllowedRandomMissionTypes", MissionPrefab.GetAllMultiplayerSelectableMissionTypes().ToArray()).ToList();
|
||||
|
||||
ServerName = doc.Root.GetAttributeString("name", "");
|
||||
if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
|
||||
|
||||
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;
|
||||
if (AllowedRandomMissionTypes.Contains(Tags.MissionTypeAll))
|
||||
{
|
||||
|
||||
@@ -168,6 +168,16 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanVoteToStartRound(Client client)
|
||||
{
|
||||
return !client.AFK || !GameMain.Server.ServerSettings.AllowAFK;
|
||||
}
|
||||
|
||||
public bool CanVoteToEndRound(Client client)
|
||||
{
|
||||
return client.HasSpawned && client.InGame;
|
||||
}
|
||||
|
||||
private bool ShouldRejectVote(Client sender, VoteType voteType)
|
||||
{
|
||||
if (rejectedVoteTimes.ContainsKey(sender))
|
||||
@@ -415,8 +425,8 @@ namespace Barotrauma
|
||||
msg.WriteBoolean(GameMain.Server.ServerSettings.AllowEndVoting);
|
||||
if (GameMain.Server.ServerSettings.AllowEndVoting)
|
||||
{
|
||||
msg.WriteByte((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned && c.GetVote<bool>(VoteType.EndRound)));
|
||||
msg.WriteByte((byte)GameMain.Server.ConnectedClients.Count(c => c.HasSpawned));
|
||||
msg.WriteByte((byte)GameMain.Server.ConnectedClients.Count(c => CanVoteToEndRound(c) && c.GetVote<bool>(VoteType.EndRound)));
|
||||
msg.WriteByte((byte)GameMain.Server.ConnectedClients.Count(c => CanVoteToEndRound(c)));
|
||||
}
|
||||
|
||||
msg.WriteBoolean(GameMain.Server.ServerSettings.AllowVoteKick);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
@@ -64,9 +66,24 @@ namespace Barotrauma.Steam
|
||||
case bool hasPassword when key == "HasPassword":
|
||||
Steamworks.SteamServer.Passworded = hasPassword;
|
||||
return;
|
||||
case string serverMessage when key == "message":
|
||||
int maxValueLength = 255;
|
||||
int totalMaxLength = 2000;
|
||||
int chunkIndex = 0;
|
||||
for (int charIndex = 0; charIndex < serverMessage.Length && charIndex < totalMaxLength; charIndex += maxValueLength)
|
||||
{
|
||||
Steamworks.SteamServer.SetKey(
|
||||
$"message{chunkIndex}",
|
||||
serverMessage.Substring(charIndex, Math.Min(maxValueLength, serverMessage.Length - charIndex)));
|
||||
chunkIndex++;
|
||||
}
|
||||
return;
|
||||
case IEnumerable<ContentPackage> contentPackages:
|
||||
//a2s seems to break if too much data is added (seems to be related to MTU?)
|
||||
//let's restrict the number of packages to 10, clients can use packagecount to tell when the list has been truncated
|
||||
const int MaxPackagesToList = 10;
|
||||
int index = 0;
|
||||
foreach (var contentPackage in contentPackages)
|
||||
foreach (var contentPackage in contentPackages.Take(MaxPackagesToList))
|
||||
{
|
||||
Steamworks.SteamServer.SetKey(
|
||||
$"contentpackage{index}",
|
||||
|
||||
Reference in New Issue
Block a user