Unstable 1.8.4.0
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -40,7 +41,7 @@ namespace Barotrauma
|
||||
{
|
||||
matchingData.ApplyPermadeath();
|
||||
|
||||
if (GameMain.Server is { ServerSettings.IronmanMode: true })
|
||||
if (GameMain.Server?.ServerSettings is { IronmanModeActive: true })
|
||||
{
|
||||
mpCampaign.SaveSingleCharacter(matchingData);
|
||||
}
|
||||
@@ -85,5 +86,16 @@ namespace Barotrauma
|
||||
{
|
||||
GameServer.Log($"{GameServer.CharacterLogName(this)} has gained the talent '{talentPrefab.DisplayName}'", ServerLog.MessageType.Talent);
|
||||
}
|
||||
|
||||
private void SyncInGameEditables(Item item)
|
||||
{
|
||||
foreach (ItemComponent itemComponent in item.Components)
|
||||
{
|
||||
foreach (var serializableProperty in SerializableProperty.GetProperties<InGameEditable>(itemComponent))
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(item, new Item.ChangePropertyEventData(serializableProperty, itemComponent));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,16 +21,16 @@ namespace Barotrauma
|
||||
CauseOfDeath = null;
|
||||
}
|
||||
|
||||
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel)
|
||||
partial void OnSkillChanged(Identifier skillIdentifier, float prevLevel, float newLevel, bool forceNotification)
|
||||
{
|
||||
if (Character == null || Character.Removed) { return; }
|
||||
if (!prevSentSkill.ContainsKey(skillIdentifier))
|
||||
{
|
||||
prevSentSkill[skillIdentifier] = prevLevel;
|
||||
}
|
||||
if (Math.Abs(prevSentSkill[skillIdentifier] - newLevel) > 0.01f)
|
||||
if (Math.Abs(prevSentSkill[skillIdentifier] - newLevel) > 0.1f || forceNotification)
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdateSkillsEventData());
|
||||
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.UpdateSkillsEventData(skillIdentifier, forceNotification));
|
||||
prevSentSkill[skillIdentifier] = newLevel;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
@@ -98,6 +100,8 @@ namespace Barotrauma
|
||||
msg.WriteInt32(ExperiencePoints);
|
||||
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
|
||||
msg.WriteBoolean(PermanentlyDead);
|
||||
msg.WriteInt32(TalentRefundPoints);
|
||||
msg.WriteInt32(TalentResetCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
@@ -298,17 +299,12 @@ namespace Barotrauma
|
||||
Kill(causeOfDeath.type, causeOfDeath.affliction);
|
||||
}
|
||||
break;
|
||||
case EventType.ConfirmTalentRefund:
|
||||
if (!CanManageTalents(c)) { return; }
|
||||
Info?.RefundTalents();
|
||||
break;
|
||||
case EventType.UpdateTalents:
|
||||
if (c.Character != this)
|
||||
{
|
||||
if (!IsBot || !c.HasPermission(ClientPermissions.ManageBotTalents))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("Received a character update message from a client who's not controlling the character");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!CanManageTalents(c)) { return; }
|
||||
|
||||
// get the full list of talents from the player, only give the ones
|
||||
// that are not already given (or otherwise not viable)
|
||||
@@ -332,6 +328,22 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
bool CanManageTalents(Client client)
|
||||
{
|
||||
if (client.Character != this)
|
||||
{
|
||||
if (client.TeamID != TeamID || !IsBot || !client.HasPermission(ClientPermissions.ManageBotTalents) || client.Spectating)
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.Log("A client tried to manage talents of a character they don't control or have permission to manage");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerWritePosition(ReadWriteMessage tempBuffer, Client c)
|
||||
@@ -377,8 +389,15 @@ namespace Barotrauma
|
||||
tempBuffer.WriteBoolean(shoot);
|
||||
tempBuffer.WriteBoolean(use);
|
||||
|
||||
tempBuffer.WriteBoolean(AnimController is HumanoidAnimController { Crouching: true });
|
||||
|
||||
if (AnimController is HumanoidAnimController humanAnim)
|
||||
{
|
||||
tempBuffer.WriteBoolean(humanAnim.Crouching);
|
||||
}
|
||||
else if (AnimController is FishAnimController fishAnim)
|
||||
{
|
||||
tempBuffer.WriteBoolean(fishAnim.Reverse);
|
||||
}
|
||||
|
||||
tempBuffer.WriteBoolean(attack);
|
||||
|
||||
Vector2 relativeCursorPos = cursorPosition - AimRefPosition;
|
||||
@@ -409,21 +428,28 @@ namespace Barotrauma
|
||||
tempBuffer.WriteSingle(SimPosition.Y);
|
||||
float MaxVel = NetConfig.MaxPhysicsBodyVelocity;
|
||||
AnimController.Collider.LinearVelocity = new Vector2(
|
||||
MathHelper.Clamp(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel),
|
||||
MathHelper.Clamp(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel));
|
||||
NetConfig.Quantize(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12),
|
||||
NetConfig.Quantize(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12));
|
||||
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.X, -MaxVel, MaxVel, 12);
|
||||
tempBuffer.WriteRangedSingle(AnimController.Collider.LinearVelocity.Y, -MaxVel, MaxVel, 12);
|
||||
|
||||
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation || !AnimController.Collider.PhysEnabled;
|
||||
AnimController.TargetMovement = new Vector2(
|
||||
NetConfig.Quantize(AnimController.TargetMovement.X, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12),
|
||||
NetConfig.Quantize(AnimController.TargetMovement.Y, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12));
|
||||
tempBuffer.WriteRangedSingle(AnimController.TargetMovement.X, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12);
|
||||
tempBuffer.WriteRangedSingle(AnimController.TargetMovement.Y, -Ragdoll.MAX_SPEED, Ragdoll.MAX_SPEED, 12);
|
||||
|
||||
bool fixedRotation = AnimController.Collider.FarseerBody.FixedRotation;
|
||||
tempBuffer.WriteBoolean(fixedRotation);
|
||||
if (!fixedRotation)
|
||||
{
|
||||
tempBuffer.WriteSingle(AnimController.Collider.Rotation);
|
||||
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
|
||||
AnimController.Collider.AngularVelocity = 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);
|
||||
}
|
||||
|
||||
|
||||
tempBuffer.WriteBoolean(AnimController.IgnorePlatforms);
|
||||
|
||||
bool writeStatus = healthUpdateTimer <= 0.0f;
|
||||
tempBuffer.WriteBoolean(writeStatus);
|
||||
if (writeStatus)
|
||||
@@ -463,21 +489,19 @@ namespace Barotrauma
|
||||
break;
|
||||
case CharacterStatusEventData statusEventData:
|
||||
WriteStatus(msg, statusEventData.ForceAfflictionData);
|
||||
msg.WriteBoolean(GodMode);
|
||||
break;
|
||||
case UpdateSkillsEventData _:
|
||||
if (Info?.Job == null)
|
||||
case UpdateSkillsEventData updateSkillsData:
|
||||
if (Info?.Job is { } job)
|
||||
{
|
||||
msg.WriteByte((byte)0);
|
||||
msg.WriteIdentifier(updateSkillsData.SkillIdentifier);
|
||||
msg.WriteBoolean(updateSkillsData.ForceNotification);
|
||||
//don't use Character.GetSkillLevel here, because it applies all the temporary boosts from items and afflictions on the skill level
|
||||
msg.WriteSingle(job.GetSkillLevel(updateSkillsData.SkillIdentifier));
|
||||
}
|
||||
else
|
||||
{
|
||||
var skills = Info.Job.GetSkills();
|
||||
msg.WriteByte((byte)skills.Count());
|
||||
foreach (Skill skill in skills)
|
||||
{
|
||||
msg.WriteIdentifier(skill.Identifier);
|
||||
msg.WriteSingle(skill.Level);
|
||||
}
|
||||
msg.WriteIdentifier(Identifier.Empty);
|
||||
}
|
||||
break;
|
||||
case IAttackEventData attackEventData:
|
||||
@@ -501,7 +525,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:
|
||||
@@ -555,6 +586,7 @@ namespace Barotrauma
|
||||
break;
|
||||
case UpdateExperienceEventData _:
|
||||
msg.WriteInt32(Info.ExperiencePoints);
|
||||
msg.WriteInt32(info.AdditionalTalentPoints);
|
||||
break;
|
||||
case UpdateTalentsEventData _:
|
||||
msg.WriteUInt16((ushort)characterTalents.Count);
|
||||
@@ -565,9 +597,16 @@ namespace Barotrauma
|
||||
}
|
||||
break;
|
||||
case UpdateMoneyEventData _:
|
||||
msg.WriteInt32(GameMain.GameSession.Campaign.GetWallet(c).Balance);
|
||||
msg.WriteInt32(Wallet?.Balance ?? 0);
|
||||
break;
|
||||
case UpdateRefundPointsEventData when Info is { } i:
|
||||
msg.WriteInt32(i.TalentRefundPoints);
|
||||
break;
|
||||
case ConfirmRefundEventData:
|
||||
// No data
|
||||
break;
|
||||
case UpdatePermanentStatsEventData updatePermanentStatsEventData:
|
||||
|
||||
StatTypes statType = updatePermanentStatsEventData.StatType;
|
||||
if (Info == null)
|
||||
{
|
||||
@@ -644,6 +683,7 @@ namespace Barotrauma
|
||||
{
|
||||
CharacterHealth.ServerWrite(msg);
|
||||
}
|
||||
|
||||
if (AnimController?.LimbJoints == null)
|
||||
{
|
||||
//0 limbs severed
|
||||
@@ -699,7 +739,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);
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Limb : ISerializableEntity, ISpatialEntity
|
||||
{
|
||||
/// <summary>
|
||||
/// An invisible "ghost body" used for doing lag compensation server side by allowing clients' shots to hit bodies at the positions where
|
||||
/// they "used to be" back when the client fired a weapon.
|
||||
/// </summary>
|
||||
public PhysicsBody LagCompensatedBody { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A queue of past positions of the limb.
|
||||
/// </summary>
|
||||
public Queue<PosInfo> MemState { get; } = new Queue<PosInfo>();
|
||||
|
||||
partial void InitProjSpecific(ContentXElement element)
|
||||
{
|
||||
LagCompensatedBody = new PhysicsBody(Params, findNewContacts: false)
|
||||
{
|
||||
BodyType = FarseerPhysics.BodyType.Static,
|
||||
CollisionCategories = Physics.CollisionLagCompensationBody,
|
||||
CollidesWith = Physics.CollisionNone,
|
||||
UserData = this
|
||||
};
|
||||
}
|
||||
|
||||
partial void UpdateProjSpecific(float deltaTime)
|
||||
{
|
||||
if (GameMain.Server == null) { return; }
|
||||
|
||||
MemState.Enqueue(new PosInfo(body.SimPosition, body.Rotation, body.LinearVelocity, body.AngularVelocity, (float)Timing.TotalTime));
|
||||
|
||||
//clear old states
|
||||
while (
|
||||
MemState.Any() &&
|
||||
MemState.Peek().Timestamp < Timing.TotalTime - GameMain.Server.ServerSettings.MaxLagCompensationSeconds)
|
||||
{
|
||||
MemState.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetLagCompensatedBodyPositions(Client client)
|
||||
{
|
||||
if (GameMain.Server == null) { return; }
|
||||
//convert from milliseconds to seconds, assume latency is symmetrical (time from client to server is half of the roundtrip time / ping)
|
||||
float latency = client.Ping / 1000.0f / 2;
|
||||
float time = (float)Timing.TotalTime - MathUtils.Min(latency, GameMain.Server.ServerSettings.MaxLagCompensationSeconds);
|
||||
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
foreach (var limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.body.Enabled == false || limb.IgnoreCollisions) { continue; }
|
||||
var matchingState = limb.MemState.FirstOrDefault(l => l.Timestamp <= time);
|
||||
if (matchingState == null) { continue; }
|
||||
limb.LagCompensatedBody.SetTransformIgnoreContacts(matchingState.Position, matchingState.Rotation ?? 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific()
|
||||
{
|
||||
LagCompensatedBody.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,11 +377,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
|
||||
{
|
||||
@@ -390,13 +390,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
|
||||
{
|
||||
@@ -498,14 +498,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) =>
|
||||
{
|
||||
@@ -716,9 +712,7 @@ namespace Barotrauma
|
||||
|
||||
ShowQuestionPrompt("Console command permissions to revoke from \"" + client.Name + "\"? You may enter multiple commands separated with a space.", (commandsStr) =>
|
||||
{
|
||||
Identifier[] splitCommands = commandsStr.Split(' ')
|
||||
.Select(s => s.Trim())
|
||||
.ToIdentifiers().ToArray();
|
||||
Identifier[] splitCommands = commandsStr.ToIdentifiers(separator: " ").ToArray();
|
||||
List<Command> revokedCommands = new List<Command>();
|
||||
bool revokeAll = splitCommands.Length > 0 && splitCommands[0] == "all";
|
||||
if (revokeAll)
|
||||
@@ -1351,14 +1345,14 @@ namespace Barotrauma
|
||||
|
||||
commands.Add(new Command("mission", "mission [name]: Select the mission type for the next round.", (string[] args) =>
|
||||
{
|
||||
GameMain.NetLobbyScreen.MissionTypeName = string.Join(" ", args);
|
||||
NewMessage("Set mission to " + GameMain.NetLobbyScreen.MissionTypeName, Color.Cyan);
|
||||
GameMain.NetLobbyScreen.MissionTypes = args.ToIdentifiers();
|
||||
NewMessage("Set mission to " + string.Join(",", GameMain.NetLobbyScreen.MissionTypes), Color.Cyan);
|
||||
},
|
||||
() =>
|
||||
{
|
||||
return new string[][]
|
||||
{
|
||||
Enum.GetNames(typeof(MissionType))
|
||||
MissionPrefab.GetAllMultiplayerSelectableMissionTypes().Select(id => id.Value).ToArray()
|
||||
};
|
||||
}));
|
||||
|
||||
@@ -1404,10 +1398,7 @@ namespace Barotrauma
|
||||
AssignOnExecute("respawnnow", (string[] args) =>
|
||||
{
|
||||
if (GameMain.Server?.RespawnManager == null) { return; }
|
||||
if (GameMain.Server.RespawnManager.CurrentState != RespawnManager.State.Transporting)
|
||||
{
|
||||
GameMain.Server.RespawnManager.ForceRespawn();
|
||||
}
|
||||
GameMain.Server.RespawnManager.ForceRespawn();
|
||||
});
|
||||
|
||||
commands.Add(new Command("startgame|startround|start", "start/startgame/startround: Start a new round.", (string[] args) =>
|
||||
@@ -1416,7 +1407,7 @@ namespace Barotrauma
|
||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign &&
|
||||
GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
|
||||
{
|
||||
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath, client: null);
|
||||
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.DataPath, client: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1425,7 +1416,9 @@ namespace Barotrauma
|
||||
MultiPlayerCampaign.StartCampaignSetup();
|
||||
return;
|
||||
}
|
||||
if (!GameMain.Server.TryStartGame()) { NewMessage("Failed to start a new round", Color.Yellow); }
|
||||
|
||||
var result = GameMain.Server.TryStartGame();
|
||||
if (result != GameServer.TryStartGameResult.Success) { NewMessage($"Failed to start a new round: {TextManager.Get($"TryStartGameError.{result}")}", Color.Yellow); }
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1614,18 +1607,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) =>
|
||||
@@ -1714,7 +1695,34 @@ namespace Barotrauma
|
||||
SpawnItem(args, cursorWorldPos, client.Character, out string errorMsg);
|
||||
if (!string.IsNullOrWhiteSpace(errorMsg))
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage(errorMsg, client);
|
||||
GameMain.Server.SendConsoleMessage(errorMsg, client, Color.Red);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"give",
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
if (client.Character == null)
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage("No character is selected!", client, Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Length == 0)
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage("Please give the name or identifier of the item to spawn.", client, Color.Red);
|
||||
return;
|
||||
}
|
||||
|
||||
var modifiedArgs = new List<string>(args);
|
||||
modifiedArgs.Insert(1, "inventory");
|
||||
|
||||
SpawnItem(modifiedArgs.ToArray(), cursorWorldPos, client.Character, out string errorMsg);
|
||||
if (!string.IsNullOrWhiteSpace(errorMsg))
|
||||
{
|
||||
GameMain.Server.SendConsoleMessage(errorMsg, client, Color.Red);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1770,11 +1778,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);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1832,14 +1846,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);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1897,24 +1913,25 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"healme",
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
bool healAll = args.Length > 0 && args[0].Equals("all", StringComparison.OrdinalIgnoreCase);
|
||||
if (client.Character != null)
|
||||
{
|
||||
HealCharacter(client.Character, healAll, client);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
AssignOnClientRequestExecute(
|
||||
"heal",
|
||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||
{
|
||||
bool healAll = args.Length > 1 && args[1].Equals("all", StringComparison.OrdinalIgnoreCase);
|
||||
Character healedCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(healAll ? args.Take(args.Length - 1).ToArray() : args);
|
||||
if (healedCharacter != null)
|
||||
{
|
||||
healedCharacter.SetAllDamage(0.0f, 0.0f, 0.0f);
|
||||
healedCharacter.Oxygen = 100.0f;
|
||||
healedCharacter.Bloodloss = 0.0f;
|
||||
healedCharacter.SetStun(0.0f, true);
|
||||
if (healAll)
|
||||
{
|
||||
healedCharacter.CharacterHealth.RemoveAllAfflictions();
|
||||
}
|
||||
}
|
||||
HandleCommandForCrewOrSingleCharacter(args, (Character targetCharacter) => HealCharacter(targetCharacter, healAll, client), client);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1934,7 +1951,7 @@ namespace Barotrauma
|
||||
|
||||
// If killed in ironman mode, the character has been wiped from the save mid-round, so its
|
||||
// original data needs to be restored to the save file (without making a backup of the dead character)
|
||||
if (GameMain.Server.ServerSettings.IronmanMode && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
|
||||
if (GameMain.Server.ServerSettings is { IronmanModeActive: true } && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
|
||||
{
|
||||
if (mpCampaign.RestoreSingleCharacterFromBackup(c) is CharacterCampaignData characterToRestore)
|
||||
{
|
||||
@@ -2546,6 +2563,7 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Skill skill in character.Info.Job.GetSkills())
|
||||
{
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdateSkillsEventData(skill.Identifier, forceNotification: true));
|
||||
character.Info.SetSkillLevel(skill.Identifier, level);
|
||||
}
|
||||
GameMain.Server.SendConsoleMessage($"Set all {character.Name}'s skills to {level}", senderClient);
|
||||
@@ -2553,10 +2571,10 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
character.Info.SetSkillLevel(skillIdentifier, level);
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdateSkillsEventData(skillIdentifier, forceNotification: true));
|
||||
GameMain.Server.SendConsoleMessage($"Set {character.Name}'s {skillIdentifier} level to {level}", senderClient);
|
||||
}
|
||||
|
||||
GameMain.NetworkMember.CreateEntityEvent(character, new Character.UpdateSkillsEventData());
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -2699,6 +2717,11 @@ namespace Barotrauma
|
||||
{
|
||||
GameMain.Server.CreateEntityEvent(wall);
|
||||
}
|
||||
foreach (Hull hull in Hull.HullList)
|
||||
{
|
||||
if (hull.IdFreed) { continue; }
|
||||
hull.CreateStatusEvent();
|
||||
}
|
||||
}));
|
||||
commands.Add(new Command("stallfiletransfers", "stallfiletransfers [seconds]: A debug command that makes all file transfers take at least the specified duration.", (string[] args) =>
|
||||
{
|
||||
|
||||
+64
-12
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -16,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
|
||||
@@ -51,29 +58,59 @@ 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();
|
||||
}
|
||||
|
||||
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> targets, float duration)
|
||||
{
|
||||
foreach (Entity e in targets)
|
||||
if (targets == null || targets.None())
|
||||
{
|
||||
if (e is not Character character || !character.IsRemotePlayer) { continue; }
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (targetClient != null)
|
||||
//if the action doesn't target anyone in specific, it's shown to every client
|
||||
foreach (var client in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
if (lastActiveAction.ContainsKey(targetClient) &&
|
||||
lastActiveAction[targetClient].ParentEvent != ParentEvent &&
|
||||
Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + duration)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (IsBlockedByAnotherConversation(client, duration)) { return true; }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (Entity e in targets)
|
||||
{
|
||||
if (e is not Character character || !character.IsRemotePlayer) { continue; }
|
||||
Client targetClient = GameMain.Server.ConnectedClients.Find(c => c.Character == character);
|
||||
if (targetClient != null && IsBlockedByAnotherConversation(targetClient, duration)) { return true; }
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsBlockedByAnotherConversation(Client targetClient, float duration)
|
||||
{
|
||||
if (lastActiveAction.ContainsKey(targetClient) &&
|
||||
!lastActiveAction[targetClient].ParentEvent.IsFinished &&
|
||||
lastActiveAction[targetClient].ParentEvent != ParentEvent &&
|
||||
Timing.TotalTime < lastActiveAction[targetClient].lastActiveTime + duration)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -91,6 +128,7 @@ namespace Barotrauma
|
||||
{
|
||||
targetClients.Add(targetClient);
|
||||
lastActiveAction[targetClient] = this;
|
||||
lastActiveTime = Timing.TotalTime;
|
||||
ServerWrite(speaker, targetClient, interrupt);
|
||||
}
|
||||
}
|
||||
@@ -99,12 +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);
|
||||
}
|
||||
}
|
||||
@@ -112,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);
|
||||
|
||||
@@ -1,20 +1,50 @@
|
||||
using System.Collections.Generic;
|
||||
#nullable enable
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class CombatMission
|
||||
{
|
||||
class KillCount
|
||||
{
|
||||
public readonly Character Victim;
|
||||
public readonly Client? VictimClient;
|
||||
public readonly Character? Killer;
|
||||
public readonly Client? KillerClient;
|
||||
public KillCount(Character victim, Character? killer)
|
||||
{
|
||||
Victim = victim;
|
||||
VictimClient = GameMain.Server.ConnectedClients.FirstOrDefault(c => victim.IsClientOwner(c));
|
||||
Killer = killer;
|
||||
if (killer != null)
|
||||
{
|
||||
KillerClient = GameMain.Server.ConnectedClients.FirstOrDefault(c => killer.IsClientOwner(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const float RoundEndDuration = 5.0f;
|
||||
|
||||
private readonly bool[] teamDead = new bool[2];
|
||||
|
||||
/// <summary>
|
||||
/// Lists of characters currently alive in the teams
|
||||
/// </summary>
|
||||
private List<Character>[] crews;
|
||||
|
||||
private bool initialized = false;
|
||||
/// <summary>
|
||||
/// List of all kills (of the characters in either team) during the round
|
||||
/// </summary>
|
||||
private readonly List<KillCount> kills = new List<KillCount>();
|
||||
|
||||
private float roundEndTimer;
|
||||
|
||||
private float timeInTargetSubmarineTimer;
|
||||
|
||||
public override LocalizedString Description
|
||||
{
|
||||
get
|
||||
@@ -28,51 +58,16 @@ namespace Barotrauma
|
||||
|
||||
protected override void UpdateMissionSpecific(float deltaTime)
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
crews[0].Clear();
|
||||
crews[1].Clear();
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.TeamID == CharacterTeamType.Team1)
|
||||
{
|
||||
crews[0].Add(character);
|
||||
}
|
||||
else if (character.TeamID == CharacterTeamType.Team2)
|
||||
{
|
||||
crews[1].Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
if (crews[0].Count == 0 || crews[1].Count == 0)
|
||||
{
|
||||
//if there are no characters in either crew, end the round
|
||||
teamDead[0] = teamDead[1] = true;
|
||||
state = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
teamDead[0] = crews[0].All(c => c.IsDead || c.IsIncapacitated);
|
||||
teamDead[1] = crews[1].All(c => c.IsDead || c.IsIncapacitated);
|
||||
if (teamDead[0] && teamDead[1]) { state = 1; }
|
||||
}
|
||||
CheckTeamCharacters();
|
||||
|
||||
if (state == 0)
|
||||
{
|
||||
CheckWinCondition(deltaTime);
|
||||
for (int i = 0; i < teamDead.Length; i++)
|
||||
{
|
||||
if (!teamDead[i] && teamDead[1 - i])
|
||||
{
|
||||
//make sure nobody in the other team can be revived because that would be pretty weird
|
||||
crews[1 - i].ForEach(c => { if (!c.IsDead) c.Kill(CauseOfDeathType.Unknown, null); });
|
||||
|
||||
GameMain.GameSession.WinningTeam = i == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2;
|
||||
|
||||
//state 1 = team 1 won, 2 = team 2 won
|
||||
State = i + 1;
|
||||
SetWinningTeam(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -85,7 +80,7 @@ namespace Barotrauma
|
||||
if (teamDead[0] && teamDead[1])
|
||||
{
|
||||
GameMain.GameSession.WinningTeam = CharacterTeamType.None;
|
||||
if (GameMain.Server != null) { GameMain.Server.EndGame(); }
|
||||
GameMain.Server?.EndGame();
|
||||
}
|
||||
else if (GameMain.GameSession.WinningTeam != CharacterTeamType.None)
|
||||
{
|
||||
@@ -93,5 +88,180 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTeamCharacters()
|
||||
{
|
||||
for (int i = 0; i < crews.Length; i++)
|
||||
{
|
||||
foreach (var character in crews[i])
|
||||
{
|
||||
if (character.IsDead)
|
||||
{
|
||||
AddKill(character);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crews[0].Clear();
|
||||
crews[1].Clear();
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.IsDead) { continue; }
|
||||
if (character.TeamID == CharacterTeamType.Team1)
|
||||
{
|
||||
crews[0].Add(character);
|
||||
}
|
||||
else if (character.TeamID == CharacterTeamType.Team2)
|
||||
{
|
||||
crews[1].Add(character);
|
||||
}
|
||||
if (character.IsBot && character.AIController is HumanAIController humanAi)
|
||||
{
|
||||
if (!humanAi.ObjectiveManager.HasOrder<AIObjectiveFightIntruders>(o => o.TargetCharactersInOtherSubs) &&
|
||||
OrderPrefab.Prefabs.TryGet(Tags.AssaultEnemyOrder, out OrderPrefab? assaultOrder))
|
||||
{
|
||||
character.SetOrder(assaultOrder.CreateInstance(
|
||||
OrderPrefab.OrderTargetType.Entity, orderGiver: null).WithManualPriority(CharacterInfo.HighestManualOrderPriority),
|
||||
isNewOrder: true, speak: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckWinCondition(float deltaTime)
|
||||
{
|
||||
switch (winCondition)
|
||||
{
|
||||
case WinCondition.LastManStanding:
|
||||
if (crews[0].Count == 0 || crews[1].Count == 0)
|
||||
{
|
||||
//if there are no characters in either crew, end the round
|
||||
teamDead[0] = teamDead[1] = true;
|
||||
state = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
teamDead[0] = crews[0].All(c => c.IsDead || c.IsIncapacitated);
|
||||
teamDead[1] = crews[1].All(c => c.IsDead || c.IsIncapacitated);
|
||||
if (teamDead[0] && teamDead[1]) { state = 1; }
|
||||
}
|
||||
break;
|
||||
case WinCondition.KillCount:
|
||||
//no need to do anything, kills are counted in AddKill
|
||||
break;
|
||||
case WinCondition.ControlSubmarine:
|
||||
CheckTargetSubmarineControl(deltaTime);
|
||||
break;
|
||||
}
|
||||
CheckScore();
|
||||
}
|
||||
|
||||
private void CheckScore()
|
||||
{
|
||||
for (int i = 0; i < crews.Length; i++)
|
||||
{
|
||||
if (Scores[i] >= WinScore)
|
||||
{
|
||||
SetWinningTeam(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckTargetSubmarineControl(float deltaTime)
|
||||
{
|
||||
if (targetSubmarine == null) { return; }
|
||||
|
||||
//score updates at 1 second intervals, so the score represents the time in seconds
|
||||
timeInTargetSubmarineTimer += deltaTime;
|
||||
if (timeInTargetSubmarineTimer < 1.0f)
|
||||
{
|
||||
return;
|
||||
}
|
||||
timeInTargetSubmarineTimer = 0.0f;
|
||||
|
||||
bool crew1InSubmarine = crews[0].Any(c => c.Submarine == targetSubmarine);
|
||||
bool crew2InSubmarine = crews[1].Any(c => c.Submarine == targetSubmarine);
|
||||
|
||||
for (int i = 0; i < crews.Length; i++)
|
||||
{
|
||||
if (crews[i].Any(c => c.Submarine == targetSubmarine) &&
|
||||
crews[1 - i].None(c => c.Submarine == targetSubmarine))
|
||||
{
|
||||
Scores[i]++;
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddToScore(CharacterTeamType team, int amount)
|
||||
{
|
||||
if (!HasWinScore) { return; }
|
||||
int index;
|
||||
switch (team)
|
||||
{
|
||||
case CharacterTeamType.Team1:
|
||||
index = 0;
|
||||
break;
|
||||
case CharacterTeamType.Team2:
|
||||
index = 1;
|
||||
break;
|
||||
default:
|
||||
DebugConsole.AddSafeError($"Attempted to increase the score of an invalid team ({team}).");
|
||||
return;
|
||||
}
|
||||
Scores[index] = MathHelper.Clamp(Scores[index] + amount, 0, WinScore);
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
}
|
||||
|
||||
private void AddKill(Character character)
|
||||
{
|
||||
kills.Add(new KillCount(character, character.CauseOfDeath?.Killer));
|
||||
if (winCondition == WinCondition.KillCount)
|
||||
{
|
||||
Scores[character.TeamID == CharacterTeamType.Team1 ? 1 : 0] += PointsPerKill;
|
||||
}
|
||||
GameMain.Server?.UpdateMissionState(this);
|
||||
}
|
||||
|
||||
private void SetWinningTeam(int teamIndex)
|
||||
{
|
||||
//state 1 = team 1 won, 2 = team 2 won
|
||||
State = teamIndex + 1;
|
||||
GameMain.GameSession.WinningTeam = teamIndex == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2;
|
||||
}
|
||||
|
||||
public override void ServerWrite(IWriteMessage msg)
|
||||
{
|
||||
base.ServerWrite(msg);
|
||||
msg.WriteUInt16((ushort)Scores[0]);
|
||||
msg.WriteUInt16((ushort)Scores[1]);
|
||||
|
||||
IEnumerable<Client> uniqueClients = kills
|
||||
.Select(k => k.VictimClient)
|
||||
.Union(kills.Select(k => k.KillerClient))
|
||||
.NotNull();
|
||||
msg.WriteVariableUInt32((uint)uniqueClients.Count());
|
||||
foreach (Client client in uniqueClients)
|
||||
{
|
||||
msg.WriteByte(client.SessionId);
|
||||
msg.WriteVariableUInt32((uint)kills.Count(k => k.VictimClient == client));
|
||||
msg.WriteVariableUInt32((uint)kills.Count(k => k.KillerClient == client));
|
||||
}
|
||||
|
||||
IEnumerable<CharacterInfo> uniqueBots = kills
|
||||
.Select(k => k.Killer)
|
||||
.Union(kills.Select(k => k.Victim))
|
||||
.NotNull()
|
||||
.Where(c => c.Info != null && c.IsBot)
|
||||
.Select(c => c.Info);
|
||||
msg.WriteVariableUInt32((uint)uniqueBots.Count());
|
||||
foreach (CharacterInfo botInfo in uniqueBots)
|
||||
{
|
||||
msg.WriteUInt16(botInfo.ID);
|
||||
msg.WriteVariableUInt32((uint)kills.Count(k => k.Victim?.Info == botInfo));
|
||||
msg.WriteVariableUInt32((uint)kills.Count(k => k.Killer?.Info == botInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -12,9 +13,9 @@ namespace Barotrauma
|
||||
{
|
||||
item.WriteSpawnData(msg,
|
||||
item.ID,
|
||||
parentInventoryIDs.ContainsKey(item) ? parentInventoryIDs[item] : Entity.NullEntityID,
|
||||
parentItemContainerIndices.ContainsKey(item) ? parentItemContainerIndices[item] : (byte)0,
|
||||
inventorySlotIndices.ContainsKey(item) ? inventorySlotIndices[item] : -1);
|
||||
parentInventoryIDs.GetValueOrDefault(item, Entity.NullEntityID),
|
||||
parentItemContainerIndices.GetValueOrDefault(item, (byte)0),
|
||||
inventorySlotIndices.GetValueOrDefault(item, -1));
|
||||
}
|
||||
ServerWriteScanTargetStatus(msg);
|
||||
}
|
||||
@@ -30,7 +31,7 @@ namespace Barotrauma
|
||||
msg.WriteByte((byte)scanTargets.Count);
|
||||
foreach (var kvp in scanTargets)
|
||||
{
|
||||
msg.WriteUInt16(kvp.Key != null ? kvp.Key.ID : Entity.NullEntityID);
|
||||
msg.WriteUInt16(kvp.Key?.ID ?? Entity.NullEntityID);
|
||||
msg.WriteBoolean(kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,7 +160,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);
|
||||
@@ -241,7 +243,7 @@ namespace Barotrauma
|
||||
maxPlayers,
|
||||
ownerKey,
|
||||
ownerEndpoint);
|
||||
Server.StartServer();
|
||||
Server.StartServer(registerToServerList: true);
|
||||
|
||||
for (int i = 0; i < CommandLineArgs.Length; i++)
|
||||
{
|
||||
@@ -275,6 +277,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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+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}\".");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
{
|
||||
@@ -13,10 +14,18 @@ namespace Barotrauma.Items.Components
|
||||
msg.WriteBoolean(writeAttachData);
|
||||
if (!writeAttachData) { return; }
|
||||
|
||||
UInt16 attacherId = Entity.NullEntityID;
|
||||
if (TryExtractEventData(extraData, out AttachEventData attachEventData) &&
|
||||
attachEventData.Attacher != null)
|
||||
{
|
||||
attacherId = attachEventData.Attacher.ID;
|
||||
}
|
||||
|
||||
msg.WriteBoolean(Attached);
|
||||
msg.WriteSingle(body.SimPosition.X);
|
||||
msg.WriteSingle(body.SimPosition.Y);
|
||||
msg.WriteUInt16(item.Submarine?.ID ?? Entity.NullEntityID);
|
||||
msg.WriteUInt16(attacherId);
|
||||
}
|
||||
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
@@ -34,7 +43,7 @@ namespace Barotrauma.Items.Components
|
||||
AttachToWall();
|
||||
OnUsed.Invoke(new ItemUseInfo(item, c.Character));
|
||||
|
||||
item.CreateServerEvent(this);
|
||||
item.CreateServerEvent(this, new AttachEventData(simPosition, c.Character));
|
||||
c.Character.Inventory?.CreateNetworkEvent();
|
||||
|
||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " attached " + item.Name + " to a wall", ServerLog.MessageType.ItemInteraction);
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma.Items.Components
|
||||
private string lastSentText;
|
||||
private float sendStateTimer;
|
||||
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "The text to display on the label.", alwaysUseInstanceValues: true), Editable(100)]
|
||||
[Serialize("", IsPropertySaveable.Yes, description: "The text to display on the label.", alwaysUseInstanceValues: true), Editable(MaxLength = 100)]
|
||||
public string Text
|
||||
{
|
||||
get;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
@@ -74,7 +78,7 @@ namespace Barotrauma.Items.Components
|
||||
if (!AutoPilot)
|
||||
{
|
||||
steeringInput = newSteeringInput;
|
||||
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel("helm") / 100.0f);
|
||||
steeringAdjustSpeed = MathHelper.Lerp(0.2f, 1.0f, c.Character.GetSkillLevel(Tags.HelmSkill) / 100.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
@@ -310,7 +310,13 @@ namespace Barotrauma.Items.Components
|
||||
return wire.BackingWire.TryUnwrap(out var backingWire) ? backingWire.Name : "a wire";
|
||||
}
|
||||
|
||||
bool CanAccessAndUnlocked(Client client) => item.CanClientAccess(client) && !Locked;
|
||||
bool CanAccessAndUnlocked(Client client) =>
|
||||
!IsLocked() &&
|
||||
item.CanClientAccess(client) &&
|
||||
ClientHasRequiredItems(client);
|
||||
|
||||
bool ClientHasRequiredItems(Client client) =>
|
||||
client.Character is { } chara && HasRequiredItems(chara, addMessage: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+39
-34
@@ -11,13 +11,17 @@ namespace Barotrauma.Items.Components
|
||||
string[] elementValues = new string[customInterfaceElementList.Count];
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
if (customInterfaceElementList[i].HasPropertyName)
|
||||
var element = customInterfaceElementList[i];
|
||||
switch (element.InputType)
|
||||
{
|
||||
elementValues[i] = msg.ReadString();
|
||||
}
|
||||
else
|
||||
{
|
||||
elementStates[i] = msg.ReadBoolean();
|
||||
case CustomInterfaceElement.InputTypeOption.Number:
|
||||
case CustomInterfaceElement.InputTypeOption.Text:
|
||||
elementValues[i] = msg.ReadString();
|
||||
break;
|
||||
case CustomInterfaceElement.InputTypeOption.Button:
|
||||
case CustomInterfaceElement.InputTypeOption.TickBox:
|
||||
elementStates[i] = msg.ReadBoolean();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +30,10 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
var element = customInterfaceElementList[i];
|
||||
if (element.HasPropertyName)
|
||||
var element = customInterfaceElementList[i];
|
||||
switch (element.InputType)
|
||||
{
|
||||
if (!element.IsNumberInput)
|
||||
{
|
||||
TextChanged(element, elementValues[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
case CustomInterfaceElement.InputTypeOption.Number:
|
||||
switch (element.NumberType)
|
||||
{
|
||||
case NumberType.Int when int.TryParse(elementValues[i], out int value):
|
||||
@@ -44,16 +43,20 @@ namespace Barotrauma.Items.Components
|
||||
ValueChanged(element, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (element.ContinuousSignal)
|
||||
{
|
||||
TickBoxToggled(element, elementStates[i]);
|
||||
}
|
||||
else if (elementStates[i])
|
||||
{
|
||||
clickedButton = element;
|
||||
ButtonClicked(element);
|
||||
break;
|
||||
case CustomInterfaceElement.InputTypeOption.Text:
|
||||
TextChanged(element, elementValues[i]);
|
||||
break;
|
||||
case CustomInterfaceElement.InputTypeOption.TickBox:
|
||||
TickBoxToggled(element, elementStates[i]);
|
||||
break;
|
||||
case CustomInterfaceElement.InputTypeOption.Button:
|
||||
if (elementStates[i])
|
||||
{
|
||||
clickedButton = element;
|
||||
ButtonClicked(element);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,17 +73,19 @@ namespace Barotrauma.Items.Components
|
||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||
{
|
||||
var element = customInterfaceElementList[i];
|
||||
if (element.HasPropertyName)
|
||||
|
||||
switch (element.InputType)
|
||||
{
|
||||
msg.WriteString(element.Signal);
|
||||
}
|
||||
else if(element.ContinuousSignal)
|
||||
{
|
||||
msg.WriteBoolean(element.State);
|
||||
}
|
||||
else
|
||||
{
|
||||
msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
|
||||
case CustomInterfaceElement.InputTypeOption.Number:
|
||||
case CustomInterfaceElement.InputTypeOption.Text:
|
||||
msg.WriteString(element.Signal);
|
||||
break;
|
||||
case CustomInterfaceElement.InputTypeOption.TickBox:
|
||||
msg.WriteBoolean(element.State);
|
||||
break;
|
||||
case CustomInterfaceElement.InputTypeOption.Button:
|
||||
msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +175,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SwapItemEventData swapItemEventData:
|
||||
msg.WriteUInt16(swapItemEventData.NewId);
|
||||
msg.WriteUInt32(swapItemEventData.NewItem.UintIdentifier);
|
||||
break;
|
||||
default:
|
||||
throw error($"Unsupported event type {itemEventData.GetType().Name}");
|
||||
}
|
||||
@@ -317,7 +321,7 @@ namespace Barotrauma
|
||||
msg.WriteBoolean(tagsChanged);
|
||||
if (tagsChanged)
|
||||
{
|
||||
IEnumerable<Identifier> splitTags = Tags.Split(',').ToIdentifiers();
|
||||
IEnumerable<Identifier> splitTags = Tags.ToIdentifiers();
|
||||
msg.WriteString(string.Join(',', splitTags.Where(t => !base.Prefab.Tags.Contains(t))));
|
||||
msg.WriteString(string.Join(',', base.Prefab.Tags.Where(t => !splitTags.Contains(t))));
|
||||
}
|
||||
@@ -420,7 +424,14 @@ namespace Barotrauma
|
||||
if (!components.Contains(ic)) { return; }
|
||||
|
||||
var eventData = new ComponentStateEventData(ic, extraData);
|
||||
if (!ic.ValidateEventData(eventData)) { throw new Exception($"Component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false."); }
|
||||
if (!ic.ValidateEventData(eventData))
|
||||
{
|
||||
string errorMsg =
|
||||
$"Server-side component event creation for the item \"{Prefab.Identifier}\" failed: {typeof(T).Name}.{nameof(ItemComponent.ValidateEventData)} returned false. " +
|
||||
$"Data: {extraData?.GetType().ToString() ?? "null"}";
|
||||
GameAnalyticsManager.AddErrorEventOnce($"Item.CreateServerEvent:ValidateEventData:{Prefab.Identifier}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||
throw new Exception(errorMsg);
|
||||
}
|
||||
GameMain.Server.CreateEntityEvent(this, eventData);
|
||||
}
|
||||
|
||||
@@ -431,10 +442,12 @@ namespace Barotrauma
|
||||
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
if (!(ic is IServerSerializable)) { continue; }
|
||||
var eventData = new ComponentStateEventData(ic, ic.ServerGetEventData());
|
||||
if (!ic.ValidateEventData(eventData)) { continue; }
|
||||
GameMain.Server.CreateEntityEvent(this, eventData);
|
||||
if (ic is not IServerSerializable) { continue; }
|
||||
var eventData = ic.ServerGetEventData();
|
||||
if (eventData == null) { continue; }
|
||||
var componentData = new ComponentStateEventData(ic, eventData);
|
||||
if (!ic.ValidateEventData(componentData)) { continue; }
|
||||
GameMain.Server.CreateEntityEvent(this, componentData);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -76,6 +76,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void CreateStatusEvent()
|
||||
{
|
||||
GameMain.NetworkMember?.CreateEntityEvent(this, new StatusEventData());
|
||||
}
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed hull event: expected {nameof(Hull)}.{nameof(IEventData)}"); }
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace Barotrauma.Networking
|
||||
string[] lines;
|
||||
try
|
||||
{
|
||||
lines = File.ReadAllLines(LegacySavePath);
|
||||
lines = File.ReadAllLines(LegacySavePath, catchUnauthorizedAccessExceptions: false);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -125,7 +125,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
GameMain.Server.SendChatMessage(txt, senderClient: c, chatMode: chatMode);
|
||||
GameMain.Server.SendChatMessage(txt, senderClient: c, chatMode: chatMode, type: type == ChatMessageType.Team ? type : null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ namespace Barotrauma.Networking
|
||||
public UInt16 LastRecvLobbyUpdate
|
||||
= NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
|
||||
public bool InitialLobbyUpdateSent;
|
||||
|
||||
public UInt16 LastSentChatMsgID = 0; //last msg this client said
|
||||
public UInt16 LastRecvChatMsgID = 0; //last msg this client knows about
|
||||
|
||||
@@ -82,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;
|
||||
}
|
||||
}
|
||||
@@ -92,6 +98,7 @@ namespace Barotrauma.Networking
|
||||
public NetworkConnection Connection { get; set; }
|
||||
|
||||
public bool SpectateOnly;
|
||||
public bool AFK;
|
||||
public bool? WaitForNextRoundRespawn;
|
||||
|
||||
public int KarmaKickCount;
|
||||
@@ -102,13 +109,19 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled) { return 100.0f; }
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled || GameMain.GameSession?.GameMode is PvPMode)
|
||||
{
|
||||
return 100.0f;
|
||||
}
|
||||
if (HasPermission(ClientPermissions.KarmaImmunity)) { return 100.0f; }
|
||||
return karma;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled) { return; }
|
||||
if (GameMain.Server == null || !GameMain.Server.ServerSettings.KarmaEnabled || GameMain.GameSession?.GameMode is PvPMode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
karma = Math.Min(Math.Max(value, 0.0f), 100.0f);
|
||||
if (!MathUtils.NearlyEqual(karma, syncedKarma, 10.0f))
|
||||
{
|
||||
@@ -160,8 +173,8 @@ namespace Barotrauma.Networking
|
||||
LastSentChatMsgID = 0;
|
||||
LastRecvChatMsgID = ChatMessage.LastID;
|
||||
|
||||
LastRecvLobbyUpdate = 0;
|
||||
|
||||
LastRecvLobbyUpdate = NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID);
|
||||
InitialLobbyUpdateSent = false;
|
||||
LastRecvEntityEventID = 0;
|
||||
|
||||
UnreceivedEntityEventCount = 0;
|
||||
@@ -327,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
try
|
||||
{
|
||||
Data = File.ReadAllBytes(filePath);
|
||||
Data = File.ReadAllBytes(filePath, catchUnauthorizedAccessExceptions: false);
|
||||
}
|
||||
catch (System.IO.IOException e)
|
||||
{
|
||||
@@ -400,7 +400,7 @@ namespace Barotrauma.Networking
|
||||
if (GameMain.GameSession != null &&
|
||||
!ActiveTransfers.Any(t => t.Connection == inc.Sender && t.FileType == FileTransferType.CampaignSave))
|
||||
{
|
||||
StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.SavePath);
|
||||
StartTransfer(inc.Sender, FileTransferType.CampaignSave, GameMain.GameSession.DataPath.LoadPath);
|
||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign)
|
||||
{
|
||||
client.LastCampaignSaveSendTime = (campaign.LastSaveID, (float)Lidgren.Network.NetTime.Now);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -151,11 +151,11 @@ namespace Barotrauma
|
||||
//increase the strength of the herpes affliction in steps instead of linearly
|
||||
//otherwise clients could determine their exact karma value from the strength
|
||||
float herpesStrength = 0.0f;
|
||||
if (client.Karma < 20)
|
||||
if (client.Karma < HerpesThreshold * 0.5f)
|
||||
herpesStrength = 100.0f;
|
||||
else if (client.Karma < 30)
|
||||
else if (client.Karma < HerpesThreshold * 0.75f)
|
||||
herpesStrength = 60.0f;
|
||||
else if (client.Karma < 40.0f)
|
||||
else if (client.Karma < HerpesThreshold)
|
||||
herpesStrength = 30.0f;
|
||||
|
||||
var existingAffliction = client.Character.CharacterHealth.GetAffliction<AfflictionSpaceHerpes>(AfflictionPrefab.SpaceHerpesType);
|
||||
|
||||
+32
-15
@@ -229,7 +229,9 @@ namespace Barotrauma.Networking
|
||||
GameMain.GameSession.RoundDuration > NetConfig.RoundStartSyncDuration)
|
||||
{
|
||||
lastWarningTime = Timing.TotalTime;
|
||||
GameServer.Log("WARNING: ServerEntityEventManager is lagging behind! Last sent id: " + lastSentToAnyone.ToString() + ", latest create id: " + ID.ToString(), ServerLog.MessageType.ServerMessage);
|
||||
string warningMsg = $"WARNING: ServerEntityEventManager is lagging behind! Last sent id: {lastSentToAnyone}, latest create id: {ID}";
|
||||
warningMsg += "\n" + GetHighEventCountsWarning(events, maxEventsToList: 3);
|
||||
GameServer.Log(warningMsg, ServerLog.MessageType.ServerMessage);
|
||||
events.ForEach(e => e.ResetCreateTime());
|
||||
//TODO: reset clients if this happens, maybe do it if a majority are behind rather than all of them?
|
||||
}
|
||||
@@ -323,30 +325,20 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
//too many events for one packet
|
||||
//(normal right after a round has just started, don't show a warning if it's been less than 10 seconds)
|
||||
if (eventsToSync.Count > 200 && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 10.0)
|
||||
//(normal right after a round has just started, don't show a warning if it's been less than 30 seconds)
|
||||
if (eventsToSync.Count > 200 && GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 30.0)
|
||||
{
|
||||
if (eventsToSync.Count > 200 && !client.NeedsMidRoundSync && Timing.TotalTime > lastEventCountHighWarning + 2.0)
|
||||
{
|
||||
Color color = eventsToSync.Count > 500 ? Color.Red : Color.Orange;
|
||||
if (eventsToSync.Count < 300) { color = Color.Yellow; }
|
||||
string warningMsg = "WARNING: event count very high: " + eventsToSync.Count;
|
||||
|
||||
var sortedEvents = eventsToSync.GroupBy(e => e.Entity.ToString())
|
||||
.Select(e => new { Value = e.Key, Count = e.Count() })
|
||||
.OrderByDescending(e => e.Count);
|
||||
|
||||
int count = 1;
|
||||
foreach (var sortedEvent in sortedEvents)
|
||||
{
|
||||
warningMsg += "\n" + count + ". " + (sortedEvent.Value?.ToString() ?? "null") + " x" + sortedEvent.Count;
|
||||
count++;
|
||||
if (count > 3) { break; }
|
||||
}
|
||||
warningMsg += "\n" + GetHighEventCountsWarning(eventsToSync, maxEventsToList: 3);
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
GameServer.Log(warningMsg, ServerLog.MessageType.Error);
|
||||
}
|
||||
server.SendConsoleMessage(warningMsg, client, color);
|
||||
DebugConsole.NewMessage(warningMsg, color);
|
||||
lastEventCountHighWarning = Timing.TotalTime;
|
||||
}
|
||||
@@ -373,6 +365,31 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private string GetHighEventCountsWarning(IEnumerable<NetEntityEvent> events, int maxEventsToList)
|
||||
{
|
||||
string warningMsg = string.Empty;
|
||||
|
||||
var sortedEvents = events.GroupBy(e => e.Entity.ToString())
|
||||
.Select(e => new { Value = e.First(), Count = e.Count() })
|
||||
.OrderByDescending(e => e.Count);
|
||||
|
||||
int count = 1;
|
||||
foreach (var sortedEvent in sortedEvents)
|
||||
{
|
||||
Entity targetEntity = sortedEvent.Value.Entity;
|
||||
if (!warningMsg.IsNullOrEmpty()) { warningMsg += "\n"; }
|
||||
warningMsg += count + ". " + (targetEntity?.ToString() ?? "null") + " x" + sortedEvent.Count;
|
||||
if (targetEntity != null && targetEntity.ContentPackage != ContentPackageManager.VanillaCorePackage)
|
||||
{
|
||||
warningMsg += $" (content package: {targetEntity.ContentPackage.Name})";
|
||||
}
|
||||
count++;
|
||||
if (count > maxEventsToList) { break; }
|
||||
}
|
||||
|
||||
return warningMsg;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of events that should be sent to the client from the eventList
|
||||
/// </summary>
|
||||
|
||||
+25
-10
@@ -269,10 +269,15 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (netServer == null) { return; }
|
||||
|
||||
switch (inc.SenderConnection.Status)
|
||||
NetConnectionStatus status = inc.ReadHeader<NetConnectionStatus>();
|
||||
switch (status)
|
||||
{
|
||||
case NetConnectionStatus.Disconnected:
|
||||
LidgrenConnection? conn = connectedClients.Select(c => c.Connection).FirstOrDefault(c => c.NetConnection == inc.SenderConnection);
|
||||
|
||||
string disconnectMsg = inc.ReadString();
|
||||
var peerDisconnectPacket =
|
||||
PeerDisconnectPacket.FromLidgrenStringRepresentation(disconnectMsg).Fallback(PeerDisconnectPacket.WithReason(DisconnectReason.Unknown));
|
||||
if (conn != null)
|
||||
{
|
||||
if (conn == OwnerConnection)
|
||||
@@ -283,7 +288,7 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
Disconnect(conn, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
Disconnect(conn, peerDisconnectPacket);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -291,7 +296,7 @@ namespace Barotrauma.Networking
|
||||
PendingClient? pendingClient = pendingClients.Find(c => c.Connection is LidgrenConnection l && l.NetConnection == inc.SenderConnection);
|
||||
if (pendingClient != null)
|
||||
{
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||
RemovePendingClient(pendingClient, peerDisconnectPacket);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +337,9 @@ namespace Barotrauma.Networking
|
||||
if (status == Steamworks.AuthResponse.OK)
|
||||
{
|
||||
pendingClient.Connection.SetAccountInfo(new AccountInfo(new SteamId(steamId), new SteamId(ownerId)));
|
||||
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
|
||||
pendingClient.InitializationStep = ShouldAskForPassword(serverSettings, pendingClient.Connection)
|
||||
? ConnectionInitialization.Password
|
||||
: ConnectionInitialization.ContentPackageOrder;
|
||||
pendingClient.UpdateTime = Timing.TotalTime;
|
||||
}
|
||||
else
|
||||
@@ -440,7 +447,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (pendingClient.AccountInfo.AccountId != packet.AccountId)
|
||||
{
|
||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||
rejectClient();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -450,7 +457,9 @@ namespace Barotrauma.Networking
|
||||
pendingClient.Connection.SetAccountInfo(accountInfo);
|
||||
pendingClient.Name = packet.Name;
|
||||
pendingClient.OwnerKey = packet.OwnerKey;
|
||||
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
|
||||
pendingClient.InitializationStep = ShouldAskForPassword(serverSettings, pendingClient.Connection)
|
||||
? ConnectionInitialization.Password
|
||||
: ConnectionInitialization.ContentPackageOrder;
|
||||
}
|
||||
|
||||
void rejectClient()
|
||||
@@ -470,7 +479,7 @@ namespace Barotrauma.Networking
|
||||
if (authenticators is null
|
||||
|| !packet.AuthTicket.TryUnwrap(out var authTicket)
|
||||
|| !authenticators.TryGetValue(authTicket.Kind, out var authenticator))
|
||||
{
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
|
||||
acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
|
||||
@@ -494,10 +503,16 @@ namespace Barotrauma.Networking
|
||||
pendingClient.AuthSessionStarted = true;
|
||||
TaskPool.Add($"{nameof(LidgrenServerPeer)}.ProcessAuth", authenticator.VerifyTicket(authTicket), t =>
|
||||
{
|
||||
if (!t.TryGetResult(out AccountInfo accountInfo)
|
||||
|| accountInfo.IsNone)
|
||||
if (!t.TryGetResult(out AccountInfo accountInfo) || accountInfo.IsNone)
|
||||
{
|
||||
rejectClient();
|
||||
if (GameMain.Server.ServerSettings.RequireAuthentication)
|
||||
{
|
||||
rejectClient();
|
||||
}
|
||||
else
|
||||
{
|
||||
acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -344,7 +344,9 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
// Do nothing with the auth ticket because that should be handled by the owner peer,
|
||||
// just assume that authentication succeeded
|
||||
pendingClient.InitializationStep = serverSettings.HasPassword ? ConnectionInitialization.Password : ConnectionInitialization.ContentPackageOrder;
|
||||
pendingClient.InitializationStep = ShouldAskForPassword(serverSettings, pendingClient.Connection)
|
||||
? ConnectionInitialization.Password
|
||||
: ConnectionInitialization.ContentPackageOrder;
|
||||
pendingClient.Name = packet.Name;
|
||||
pendingClient.AuthSessionStarted = true;
|
||||
}
|
||||
|
||||
+13
@@ -359,5 +359,18 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
protected static void LogMalformedMessage()
|
||||
=> DebugConsole.ThrowError("Received malformed message from remote peer.");
|
||||
|
||||
protected bool ShouldAskForPassword(ServerSettings serverSettings, NetworkConnection connection)
|
||||
{
|
||||
if (!serverSettings.HasPassword) { return false; }
|
||||
|
||||
if (GameMain.Server is { } server && server.FindAndRemoveRecentlyDisconnectedConnection(connection))
|
||||
{
|
||||
// do not ask passwords from clients that have recently disconnected
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -8,16 +9,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
partial class RespawnManager : Entity, IServerSerializable
|
||||
{
|
||||
private DateTime despawnTime;
|
||||
|
||||
private float shuttleEmptyTimer;
|
||||
|
||||
private int pendingRespawnCount, requiredRespawnCount;
|
||||
private int prevPendingRespawnCount, prevRequiredRespawnCount;
|
||||
public bool IsShuttleInsideLevel => RespawnShuttles.Any(s => s.WorldPosition.Y < Level.Loaded.Size.Y);
|
||||
|
||||
public bool IsShuttleInsideLevel => RespawnShuttle != null && RespawnShuttle.WorldPosition.Y < Level.Loaded.Size.Y;
|
||||
|
||||
private IEnumerable<Client> GetClientsToRespawn()
|
||||
private IEnumerable<Client> GetClientsToRespawn(CharacterTeamType teamId)
|
||||
{
|
||||
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
|
||||
foreach (Client c in networkMember.ConnectedClients)
|
||||
@@ -25,6 +21,10 @@ namespace Barotrauma.Networking
|
||||
if (!c.InGame) { continue; }
|
||||
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { continue; }
|
||||
if (c.Character != null && !c.Character.IsDead) { continue; }
|
||||
if (c.TeamID != CharacterTeamType.None && c.TeamID != teamId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var matchingData = campaign?.GetClientCharacterData(c);
|
||||
|
||||
@@ -35,8 +35,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;
|
||||
@@ -44,7 +45,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; }
|
||||
@@ -63,7 +64,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 =>
|
||||
@@ -80,36 +81,52 @@ namespace Barotrauma.Networking
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<CharacterInfo> GetBotsToRespawn()
|
||||
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
|
||||
var botInfos = GameMain.GameSession.CrewManager.GetCharacterInfos()
|
||||
.Where(botInfo => botInfo.TeamID == teamId)
|
||||
//filter out players in case a player has been given control of a bot using console commands
|
||||
.Where(botInfo => GameMain.Server.ConnectedClients.None(c => c.CharacterInfo == botInfo))
|
||||
.ToList();
|
||||
|
||||
if (GameMain.Server.ServerSettings.BotSpawnMode == BotSpawnMode.Normal)
|
||||
{
|
||||
return Character.CharacterList
|
||||
.FindAll(c => c.TeamID == CharacterTeamType.Team1 && c.AIController != null && c.Info != null && c.IsDead)
|
||||
.Select(c => c.Info)
|
||||
.ToList();
|
||||
return botInfos.Where(ci => ci.Character == null || ci.Character.IsDead).ToList();
|
||||
}
|
||||
|
||||
int currPlayerCount = GameMain.Server.ConnectedClients.Count(c =>
|
||||
c.InGame &&
|
||||
(!c.SpectateOnly || (!GameMain.Server.ServerSettings.AllowSpectating && GameMain.Server.OwnerConnection != c.Connection)));
|
||||
|
||||
var existingBots = Character.CharacterList
|
||||
.FindAll(c => c.TeamID == CharacterTeamType.Team1 && c.AIController != null && c.Info != null);
|
||||
|
||||
var existingBots = Character.CharacterList.FindAll(c => c.IsBot && !c.IsDead && c.TeamID == teamId);
|
||||
int requiredBots = GameMain.Server.ServerSettings.BotCount - currPlayerCount;
|
||||
requiredBots -= existingBots.Count(b => !b.IsDead);
|
||||
|
||||
List<CharacterInfo> botsToRespawn = new List<CharacterInfo>();
|
||||
for (int i = 0; i < requiredBots; i++)
|
||||
{
|
||||
CharacterInfo botToRespawn = existingBots.Find(b => b.IsDead)?.Info;
|
||||
CharacterInfo botToRespawn = botInfos.FirstOrDefault(b => b.Character == null || b.Character.IsDead);
|
||||
if (botToRespawn == null)
|
||||
{
|
||||
botToRespawn = new CharacterInfo(CharacterPrefab.HumanSpeciesName);
|
||||
botToRespawn = new CharacterInfo(CharacterPrefab.HumanSpeciesName)
|
||||
{
|
||||
TeamID = teamId
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
botInfos.Remove(botToRespawn);
|
||||
existingBots.Remove(botToRespawn.Character);
|
||||
}
|
||||
botsToRespawn.Add(botToRespawn);
|
||||
@@ -117,15 +134,34 @@ namespace Barotrauma.Networking
|
||||
return botsToRespawn;
|
||||
}
|
||||
|
||||
private bool ShouldStartRespawnCountdown()
|
||||
private string GetRespawnShuttleText(CharacterTeamType team)
|
||||
{
|
||||
int characterToRespawnCount = GetClientsToRespawn().Count();
|
||||
if (teamSpecificStates.Count == 1)
|
||||
{
|
||||
return "respawn shuttle";
|
||||
}
|
||||
return team == CharacterTeamType.Team1 ? "respawn shuttle (team 1)" : "respawn shuttle (team 2)";
|
||||
}
|
||||
private string GetTeamNameText(CharacterTeamType team)
|
||||
{
|
||||
if (teamSpecificStates.Count == 1)
|
||||
{
|
||||
return "everyone";
|
||||
}
|
||||
return team == CharacterTeamType.Team1 ? "team 1" : "team 2";
|
||||
}
|
||||
|
||||
|
||||
private bool ShouldStartRespawnCountdown(TeamSpecificState teamSpecificState)
|
||||
{
|
||||
int characterToRespawnCount = GetClientsToRespawn(teamSpecificState.TeamID).Count();
|
||||
return ShouldStartRespawnCountdown(characterToRespawnCount);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -133,108 +169,92 @@ namespace Barotrauma.Networking
|
||||
return characterToRespawnCount >= GetMinCharactersToRespawn();
|
||||
}
|
||||
|
||||
partial void UpdateWaiting(float _)
|
||||
partial void UpdateWaiting(TeamSpecificState teamSpecificState)
|
||||
{
|
||||
if (RespawnShuttle != null)
|
||||
//no respawns in the first minute of the round - otherwise it can be that bots
|
||||
//are respawned to "fill" the spots of players who are taking a long time to load in
|
||||
if (GameMain.GameSession is { RoundDuration: < 60 })
|
||||
{
|
||||
RespawnShuttle.Velocity = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
pendingRespawnCount = GetClientsToRespawn().Count();
|
||||
requiredRespawnCount = GetMinCharactersToRespawn();
|
||||
if (pendingRespawnCount != prevPendingRespawnCount ||
|
||||
requiredRespawnCount != prevRequiredRespawnCount)
|
||||
var teamId = teamSpecificState.TeamID;
|
||||
var respawnShuttle = GetShuttle(teamId);
|
||||
if (respawnShuttle != null)
|
||||
{
|
||||
prevPendingRespawnCount = pendingRespawnCount;
|
||||
prevRequiredRespawnCount = requiredRespawnCount;
|
||||
respawnShuttle.Velocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
teamSpecificState.PendingRespawnCount = GetClientsToRespawn(teamId).Count();
|
||||
if (GameMain.GameSession?.Campaign == null)
|
||||
{
|
||||
teamSpecificState.PendingRespawnCount += GetBotsToRespawn(teamId).Count;
|
||||
}
|
||||
teamSpecificState.RequiredRespawnCount = GetMinCharactersToRespawn();
|
||||
if (teamSpecificState.PendingRespawnCount != teamSpecificState.PrevPendingRespawnCount ||
|
||||
teamSpecificState.RequiredRespawnCount != teamSpecificState.PrevRequiredRespawnCount)
|
||||
{
|
||||
teamSpecificState.PrevPendingRespawnCount = teamSpecificState.PendingRespawnCount;
|
||||
teamSpecificState.PrevRequiredRespawnCount = teamSpecificState.RequiredRespawnCount;
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
|
||||
if (RespawnCountdownStarted)
|
||||
if (teamSpecificState.RespawnCountdownStarted)
|
||||
{
|
||||
if (pendingRespawnCount == 0)
|
||||
if (teamSpecificState.PendingRespawnCount == 0)
|
||||
{
|
||||
RespawnCountdownStarted = false;
|
||||
teamSpecificState.RespawnCountdownStarted = false;
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool shouldStartCountdown = ShouldStartRespawnCountdown(pendingRespawnCount);
|
||||
bool shouldStartCountdown = ShouldStartRespawnCountdown(teamSpecificState.PendingRespawnCount);
|
||||
if (shouldStartCountdown)
|
||||
{
|
||||
RespawnCountdownStarted = true;
|
||||
if (RespawnTime < DateTime.Now)
|
||||
teamSpecificState.RespawnCountdownStarted = true;
|
||||
if (teamSpecificState.RespawnTime < DateTime.Now)
|
||||
{
|
||||
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f));
|
||||
teamSpecificState.RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, (int)(GameMain.Server.ServerSettings.RespawnInterval * 1000.0f));
|
||||
}
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (RespawnCountdownStarted && DateTime.Now > RespawnTime)
|
||||
if (teamSpecificState.RespawnCountdownStarted && DateTime.Now > teamSpecificState.RespawnTime)
|
||||
{
|
||||
DispatchShuttle();
|
||||
RespawnCountdownStarted = false;
|
||||
DispatchShuttle(teamSpecificState);
|
||||
teamSpecificState.RespawnCountdownStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatchShuttle()
|
||||
private void DispatchShuttle(TeamSpecificState teamSpecificState)
|
||||
{
|
||||
if (RespawnShuttle != null)
|
||||
if (RespawnShuttles.Any())
|
||||
{
|
||||
CurrentState = State.Transporting;
|
||||
ResetShuttle(teamSpecificState);
|
||||
teamSpecificState.CurrentState = State.Transporting;
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
if (shuttleSteering != null)
|
||||
{
|
||||
shuttleSteering.TargetVelocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
Vector2 spawnPos = FindSpawnPos();
|
||||
RespawnCharacters(spawnPos, out bool anyCharacterSpawnedInShuttle);
|
||||
if (anyCharacterSpawnedInShuttle)
|
||||
{
|
||||
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.Spawning);
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
if (spawnPos.Y > Level.Loaded.Size.Y)
|
||||
{
|
||||
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
||||
}
|
||||
else
|
||||
{
|
||||
RespawnShuttle.SetPosition(spawnPos);
|
||||
RespawnShuttle.Velocity = Vector2.Zero;
|
||||
RespawnShuttle.NeutralizeBallast();
|
||||
RespawnShuttle.EnableMaintainPosition();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
||||
}
|
||||
SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Dynamic);
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentState = State.Waiting;
|
||||
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
||||
teamSpecificState.CurrentState = State.Waiting;
|
||||
GameServer.Log($"Respawning {GetTeamNameText(teamSpecificState.TeamID)} in the main sub.", ServerLog.MessageType.Spawning);
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCharacters(shuttlePos: null, out _);
|
||||
}
|
||||
RespawnCharacters(teamSpecificState);
|
||||
}
|
||||
|
||||
partial void UpdateReturningProjSpecific(float deltaTime)
|
||||
partial void UpdateReturningProjSpecific(TeamSpecificState teamSpecificState, float deltaTime)
|
||||
{
|
||||
//speed up despawning if there's no-one inside the shuttle
|
||||
if (despawnTime > DateTime.Now + new TimeSpan(0, 0, seconds: 30) && CheckShuttleEmpty(deltaTime))
|
||||
if (teamSpecificState.DespawnTime > DateTime.Now + new TimeSpan(0, 0, seconds: 30) && CheckShuttleEmpty(deltaTime))
|
||||
{
|
||||
despawnTime = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
|
||||
teamSpecificState.DespawnTime = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
|
||||
}
|
||||
|
||||
foreach (Door door in shuttleDoors)
|
||||
foreach (Door door in shuttleDoors[teamSpecificState.TeamID])
|
||||
{
|
||||
if (door.IsOpen)
|
||||
{
|
||||
@@ -242,87 +262,71 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
var shuttleGaps = Gap.GapList.FindAll(g => g.Submarine == RespawnShuttle && g.ConnectedWall != null);
|
||||
var shuttleGaps = Gap.GapList.FindAll(g => RespawnShuttles.Contains(g.Submarine) && g.ConnectedWall != null);
|
||||
shuttleGaps.ForEach(g => Spawner.AddEntityToRemoveQueue(g));
|
||||
|
||||
var dockingPorts = Item.ItemList.FindAll(i => i.Submarine == RespawnShuttle && i.GetComponent<DockingPort>() != null);
|
||||
var dockingPorts = Item.ItemList.FindAll(i => RespawnShuttles.Contains(i.Submarine) && i.GetComponent<DockingPort>() != null);
|
||||
dockingPorts.ForEach(d => d.GetComponent<DockingPort>().Undock());
|
||||
|
||||
//shuttle has returned if the path has been traversed or the shuttle is close enough to the exit
|
||||
if (!CoroutineManager.IsCoroutineRunning("forcepos"))
|
||||
if (!IsShuttleInsideLevel || DateTime.Now > teamSpecificState.DespawnTime)
|
||||
{
|
||||
if ((shuttleSteering?.SteeringPath != null && shuttleSteering.SteeringPath.Finished)
|
||||
|| (RespawnShuttle.WorldPosition.Y + RespawnShuttle.Borders.Y > Level.Loaded.StartPosition.Y - Level.ShaftHeight &&
|
||||
Math.Abs(Level.Loaded.StartPosition.X - RespawnShuttle.WorldPosition.X) < 1000.0f))
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
CoroutineManager.StartCoroutine(
|
||||
ForceShuttleToPos(new Vector2(Level.Loaded.StartPosition.X, Level.Loaded.Size.Y + 1000.0f), 100.0f), "forcepos");
|
||||
ResetShuttle(teamSpecificState);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsShuttleInsideLevel || DateTime.Now > despawnTime)
|
||||
{
|
||||
CoroutineManager.StopCoroutines("forcepos");
|
||||
|
||||
ResetShuttle();
|
||||
|
||||
CurrentState = State.Waiting;
|
||||
GameServer.Log("The respawn shuttle has left.", ServerLog.MessageType.Spawning);
|
||||
teamSpecificState.CurrentState = State.Waiting;
|
||||
GameServer.Log($"The {GetRespawnShuttleText(teamSpecificState.TeamID)} has left.", ServerLog.MessageType.Spawning);
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCountdownStarted = false;
|
||||
ReturnCountdownStarted = false;
|
||||
teamSpecificState.RespawnCountdownStarted = false;
|
||||
teamSpecificState.ReturnCountdownStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
partial void UpdateTransportingProjSpecific(float deltaTime)
|
||||
partial void UpdateTransportingProjSpecific(TeamSpecificState teamSpecificState, float deltaTime)
|
||||
{
|
||||
if (!ReturnCountdownStarted)
|
||||
if (!teamSpecificState.ReturnCountdownStarted)
|
||||
{
|
||||
//if there are no living chracters inside, transporting can be stopped immediately
|
||||
if (CheckShuttleEmpty(deltaTime))
|
||||
{
|
||||
ReturnTime = DateTime.Now;
|
||||
ReturnCountdownStarted = true;
|
||||
teamSpecificState.ReturnTime = DateTime.Now;
|
||||
teamSpecificState.ReturnCountdownStarted = true;
|
||||
}
|
||||
else if (!ShouldStartRespawnCountdown())
|
||||
else if (!ShouldStartRespawnCountdown(teamSpecificState))
|
||||
{
|
||||
//don't start counting down until someone else needs to respawn
|
||||
ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
|
||||
despawnTime = ReturnTime + new TimeSpan(0, 0, seconds: 30);
|
||||
teamSpecificState.ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(maxTransportTime * 1000));
|
||||
teamSpecificState.DespawnTime = teamSpecificState.ReturnTime + new TimeSpan(0, 0, seconds: 30);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
ReturnCountdownStarted = true;
|
||||
teamSpecificState.ReturnCountdownStarted = true;
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
}
|
||||
}
|
||||
else if (CheckShuttleEmpty(deltaTime))
|
||||
{
|
||||
ReturnTime = DateTime.Now;
|
||||
teamSpecificState.ReturnTime = DateTime.Now;
|
||||
}
|
||||
|
||||
if (DateTime.Now > ReturnTime)
|
||||
if (DateTime.Now > teamSpecificState.ReturnTime)
|
||||
{
|
||||
if (IsShuttleInsideLevel)
|
||||
{
|
||||
GameServer.Log("The respawn shuttle is leaving.", ServerLog.MessageType.ServerMessage);
|
||||
GameServer.Log($"The {GetRespawnShuttleText(teamSpecificState.TeamID)} is leaving.", ServerLog.MessageType.ServerMessage);
|
||||
}
|
||||
CurrentState = State.Returning;
|
||||
teamSpecificState.CurrentState = State.Returning;
|
||||
|
||||
GameMain.Server.CreateEntityEvent(this);
|
||||
|
||||
RespawnCountdownStarted = false;
|
||||
teamSpecificState.RespawnCountdownStarted = false;
|
||||
maxTransportTime = GameMain.Server.ServerSettings.MaxTransportTime;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckShuttleEmpty(float deltaTime)
|
||||
{
|
||||
if (!Character.CharacterList.Any(c => c.Submarine == RespawnShuttle && !c.IsDead))
|
||||
if (RespawnShuttles.All(respawnShuttle => Character.CharacterList.None(c => c.Submarine == respawnShuttle && !c.IsDead)))
|
||||
{
|
||||
shuttleEmptyTimer += deltaTime;
|
||||
}
|
||||
@@ -333,15 +337,18 @@ namespace Barotrauma.Networking
|
||||
return shuttleEmptyTimer > 1.0f;
|
||||
}
|
||||
|
||||
private void RespawnCharacters(Vector2? shuttlePos, out bool anyCharacterSpawnedInShuttle)
|
||||
private void RespawnCharacters(TeamSpecificState teamSpecificState)
|
||||
{
|
||||
respawnedCharacters.Clear();
|
||||
|
||||
var respawnSub = RespawnShuttle ?? Submarine.MainSub;
|
||||
var teamID = teamSpecificState.TeamID;
|
||||
int teamIndex = teamID == CharacterTeamType.Team1 ? 0 : 1;
|
||||
bool anyCharacterSpawnedInShuttle = false;
|
||||
teamSpecificState.RespawnedCharacters.Clear();
|
||||
|
||||
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
|
||||
bool isPvPMode = GameMain.GameSession.GameMode is PvPMode;
|
||||
int teamCount = isPvPMode ? 2 : 1;
|
||||
|
||||
var clients = GetClientsToRespawn().ToList();
|
||||
var clients = GetClientsToRespawn(teamID).ToList();
|
||||
foreach (Client c in clients)
|
||||
{
|
||||
// Get rid of the existing character
|
||||
@@ -355,18 +362,31 @@ namespace Barotrauma.Networking
|
||||
c.CharacterInfo = matchingData.CharacterInfo;
|
||||
}
|
||||
|
||||
//all characters are in Team 1 in game modes/missions with only one team.
|
||||
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
|
||||
c.TeamID = CharacterTeamType.Team1;
|
||||
c.CharacterInfo ??= new CharacterInfo(CharacterPrefab.HumanSpeciesName, c.Name);
|
||||
|
||||
//force everyone to team 1 if there's just one team
|
||||
if (teamCount == 1)
|
||||
{
|
||||
c.TeamID = teamID;
|
||||
}
|
||||
else if (isPvPMode && c.TeamID == CharacterTeamType.None)
|
||||
{
|
||||
GameMain.Server.AssignClientToPvpTeamMidgame(c);
|
||||
}
|
||||
c.CharacterInfo.TeamID = c.TeamID;
|
||||
}
|
||||
List<CharacterInfo> characterInfos = clients.Select(c => c.CharacterInfo).ToList();
|
||||
|
||||
//bots don't respawn in the campaign
|
||||
var botsToSpawn = GetBotsToRespawn(teamID);
|
||||
if (campaign == null)
|
||||
{
|
||||
var botsToSpawn = GetBotsToRespawn();
|
||||
characterInfos.AddRange(botsToSpawn);
|
||||
foreach (var bot in botsToSpawn)
|
||||
{
|
||||
// Get rid of the existing bots' corpses
|
||||
if (bot.Character is Character character) { character.DespawnNow(); }
|
||||
}
|
||||
}
|
||||
|
||||
GameMain.Server.AssignJobs(clients);
|
||||
@@ -374,38 +394,56 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
if (campaign?.GetClientCharacterData(c) == null || c.CharacterInfo.Job == null)
|
||||
{
|
||||
c.CharacterInfo.Job = new Job(c.AssignedJob.Prefab, Rand.RandSync.Unsynced, c.AssignedJob.Variant);
|
||||
c.CharacterInfo.Job = new Job(c.AssignedJob.Prefab, isPvPMode, Rand.RandSync.Unsynced, c.AssignedJob.Variant);
|
||||
}
|
||||
}
|
||||
|
||||
//the spawnpoints where the characters will spawn
|
||||
var shuttleSpawnPoints = 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)
|
||||
var mainSubSpawnPoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
|
||||
System.Diagnostics.Debug.Assert(characterInfos.All(c => c.TeamID == teamID),
|
||||
"List of characters to respawn contained characters from the wrong team.");
|
||||
|
||||
Submarine mainSub = Submarine.MainSubs[teamIndex];
|
||||
Submarine respawnSub = null;
|
||||
|
||||
Submarine respawnShuttle = GetShuttle(teamID);
|
||||
Vector2? shuttlePos = null;
|
||||
if (respawnShuttle != null)
|
||||
{
|
||||
respawnSub = respawnShuttle;
|
||||
shuttlePos = FindSpawnPos(respawnShuttle, mainSub);
|
||||
}
|
||||
|
||||
respawnSub ??= mainSub ?? Level.Loaded.StartOutpost;
|
||||
|
||||
ItemPrefab divingSuitPrefab = null;
|
||||
if ((shuttlePos != null && Level.Loaded.GetRealWorldDepth(shuttlePos.Value.Y) > Level.DefaultRealWorldCrushDepth) ||
|
||||
Level.Loaded.GetRealWorldDepth(Submarine.MainSub.WorldPosition.Y) > Level.DefaultRealWorldCrushDepth)
|
||||
(mainSub != null && Level.Loaded.GetRealWorldDepth(mainSub.WorldPosition.Y) > Level.DefaultRealWorldCrushDepth))
|
||||
{
|
||||
divingSuitPrefab = ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t == "respawnsuitdeep"));
|
||||
}
|
||||
divingSuitPrefab ??=
|
||||
divingSuitPrefab ??=
|
||||
ItemPrefab.Prefabs.FirstOrDefault(it => it.Tags.Any(t => t == "respawnsuit")) ??
|
||||
ItemPrefab.Find(null, "divingsuit".ToIdentifier());
|
||||
ItemPrefab oxyPrefab = ItemPrefab.Find(null, "oxygentank".ToIdentifier());
|
||||
ItemPrefab scooterPrefab = ItemPrefab.Find(null, "underwaterscooter".ToIdentifier());
|
||||
ItemPrefab batteryPrefab = ItemPrefab.Find(null, "batterycell".ToIdentifier());
|
||||
|
||||
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
|
||||
//the spawnpoints where the characters will spawn
|
||||
var selectedSpawnPoints =
|
||||
isPvPMode && Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost() ?
|
||||
WayPoint.SelectOutpostSpawnPoints(characterInfos, teamID) :
|
||||
WayPoint.SelectCrewSpawnPoints(characterInfos, respawnSub);
|
||||
|
||||
anyCharacterSpawnedInShuttle = false;
|
||||
//the spawnpoints where they would spawn if they were spawned inside the main sub
|
||||
//(in order to give them appropriate ID card tags)
|
||||
var mainSubSpawnPoints = mainSub != null ? WayPoint.SelectCrewSpawnPoints(characterInfos, mainSub) : null;
|
||||
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
|
||||
|
||||
for (int i = 0; i < characterInfos.Count; i++)
|
||||
{
|
||||
bool bot = i >= clients.Count;
|
||||
var characterInfo = characterInfos[i];
|
||||
|
||||
characterInfos[i].ClearCurrentOrders();
|
||||
bool bot = botsToSpawn.Contains(characterInfo);
|
||||
characterInfo.ClearCurrentOrders();
|
||||
|
||||
CharacterCampaignData characterCampaignData = null;
|
||||
bool forceSpawnInMainSub = false;
|
||||
@@ -413,9 +451,9 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
//the client has opted to change the name of their new character
|
||||
//when the character spawns, set the client's name to match
|
||||
if (clients[i].PendingName == characterInfos[i].Name)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -428,32 +466,31 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
ReduceCharacterSkillsOnDeath(characterInfos[i]);
|
||||
characterInfos[i].RemoveSavedStatValuesOnDeath();
|
||||
characterInfos[i].CauseOfDeath = null;
|
||||
ReduceCharacterSkillsOnDeath(characterInfo);
|
||||
characterInfo.RemoveSavedStatValuesOnDeath();
|
||||
characterInfo.CauseOfDeath = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!forceSpawnInMainSub)
|
||||
if (!forceSpawnInMainSub && respawnShuttle != null)
|
||||
{
|
||||
anyCharacterSpawnedInShuttle = true;
|
||||
anyCharacterSpawnedInShuttle = true;
|
||||
}
|
||||
|
||||
var character = Character.Create(characterInfos[i], (forceSpawnInMainSub ? mainSubSpawnPoints[i] : shuttleSpawnPoints[i]).WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot);
|
||||
var character = Character.Create(characterInfo, (forceSpawnInMainSub ? mainSubSpawnPoints[i] : selectedSpawnPoints[i]).WorldPosition, characterInfo.Name, isRemotePlayer: !bot, hasAi: bot);
|
||||
characterCampaignData?.ApplyWalletData(character);
|
||||
character.TeamID = CharacterTeamType.Team1;
|
||||
character.LoadTalents();
|
||||
if (characterInfos[i].LastRewardDistribution.TryUnwrap(out int salary))
|
||||
if (characterInfo.LastRewardDistribution.TryUnwrap(out int salary))
|
||||
{
|
||||
character.Wallet.SetRewardDistribution(salary);
|
||||
}
|
||||
|
||||
respawnedCharacters.Add(character);
|
||||
teamSpecificState.RespawnedCharacters.Add(character);
|
||||
|
||||
if (bot)
|
||||
{
|
||||
GameServer.Log(string.Format("Respawning bot {0} as {1}", character.Info.Name, characterInfos[i].Job.Name), ServerLog.MessageType.Spawning);
|
||||
GameServer.Log(string.Format("Respawning bot {0} as {1}.", character.Info.Name, characterInfo.Job.Name), ServerLog.MessageType.Spawning);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -475,11 +512,18 @@ namespace Barotrauma.Networking
|
||||
clients[i].Character = character;
|
||||
character.SetOwnerClient(clients[i]);
|
||||
GameServer.Log(
|
||||
$"Respawning {GameServer.ClientLogName(clients[i])} ({clients[i].Connection.Endpoint}) as {characterInfos[i].Job.Name}", ServerLog.MessageType.Spawning);
|
||||
$"Respawning {GameServer.ClientLogName(clients[i])} ({clients[i].Connection.Endpoint}) as {characterInfo.Job.Name}.", ServerLog.MessageType.Spawning);
|
||||
}
|
||||
|
||||
if (RespawnShuttle != null && anyCharacterSpawnedInShuttle)
|
||||
if (respawnShuttle != null && anyCharacterSpawnedInShuttle)
|
||||
{
|
||||
GameServer.Log($"Dispatching the {GetRespawnShuttleText(teamID)}.", ServerLog.MessageType.Spawning);
|
||||
respawnShuttle.SetPosition(shuttlePos.Value);
|
||||
respawnShuttle.Velocity = Vector2.Zero;
|
||||
respawnShuttle.NeutralizeBallast();
|
||||
respawnShuttle.EnableMaintainPosition();
|
||||
shuttleSteering[teamID].ForEach(s => s.TargetVelocity = Vector2.Zero);
|
||||
|
||||
List<Item> newRespawnItems = new List<Item>();
|
||||
Vector2 pos = cargoSp?.Position ?? character.Position;
|
||||
if (divingSuitPrefab != null)
|
||||
@@ -513,25 +557,29 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
}
|
||||
if (respawnContainer != null)
|
||||
{
|
||||
AutoItemPlacer.RegenerateLoot(RespawnShuttle, respawnContainer);
|
||||
}
|
||||
|
||||
//try to put the items in containers in the shuttle
|
||||
foreach (var respawnItem in newRespawnItems)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(!respawnItem.Removed);
|
||||
foreach (Item shuttleItem in RespawnShuttle.GetItems(alsoFromConnectedSubs: false))
|
||||
//already in a container (a battery we just placed in a scooter?) -> don't move to a cabinet
|
||||
if (respawnItem.Container == null)
|
||||
{
|
||||
if (shuttleItem.NonInteractable || shuttleItem.NonPlayerTeamInteractable) { continue; }
|
||||
var container = shuttleItem.GetComponent<ItemContainer>();
|
||||
if (container != null && container.Inventory.TryPutItem(respawnItem, user: null))
|
||||
foreach (Item shuttleItem in respawnShuttle.GetItems(alsoFromConnectedSubs: false))
|
||||
{
|
||||
break;
|
||||
if (shuttleItem.NonInteractable || shuttleItem.NonPlayerTeamInteractable) { continue; }
|
||||
var container = shuttleItem.GetComponent<ItemContainer>();
|
||||
if (container != null && container.Inventory.TryPutItem(respawnItem, user: null))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
respawnItems.Add(respawnItem);
|
||||
teamSpecificState.RespawnItems.Add(respawnItem);
|
||||
}
|
||||
|
||||
foreach (var respawnContainer in respawnContainers[teamID])
|
||||
{
|
||||
teamSpecificState.RespawnItems.AddRange(AutoItemPlacer.RegenerateLoot(respawnShuttle, respawnContainer));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,10 +589,11 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
ReduceCharacterSkillsOnDeath(characterInfos[i], applyExtraSkillLoss: true);
|
||||
}
|
||||
WayPoint jobItemSpawnPoint = mainSubSpawnPoints != null ? mainSubSpawnPoints[i] : selectedSpawnPoints[i];
|
||||
if (characterData == null || characterData.HasSpawned)
|
||||
{
|
||||
//give the character the items they would've gotten if they had spawned in the main sub
|
||||
character.GiveJobItems(mainSubSpawnPoints[i]);
|
||||
character.GiveJobItems(isPvPMode, jobItemSpawnPoint);
|
||||
if (campaign != null)
|
||||
{
|
||||
characterData = campaign.SetClientCharacterData(clients[i]);
|
||||
@@ -559,16 +608,17 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
else
|
||||
{
|
||||
character.GiveJobItems(mainSubSpawnPoints[i]);
|
||||
character.GiveJobItems(isPvPMode, jobItemSpawnPoint);
|
||||
}
|
||||
characterData.ApplyHealthData(character);
|
||||
character.GiveIdCardTags(mainSubSpawnPoints[i]);
|
||||
character.GiveIdCardTags(jobItemSpawnPoint);
|
||||
characterData.HasSpawned = true;
|
||||
}
|
||||
|
||||
//add the ID card tags they should've gotten when spawning in the shuttle
|
||||
character.GiveIdCardTags(shuttleSpawnPoints[i], createNetworkEvent: true);
|
||||
character.GiveIdCardTags(selectedSpawnPoints[i], createNetworkEvent: true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -605,24 +655,30 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||
{
|
||||
msg.WriteRangedInteger((int)CurrentState, 0, Enum.GetNames(typeof(State)).Length);
|
||||
|
||||
switch (CurrentState)
|
||||
msg.WriteByte((byte)c.TeamID);
|
||||
foreach (var teamSpecificState in teamSpecificStates.Values)
|
||||
{
|
||||
case State.Transporting:
|
||||
msg.WriteBoolean(ReturnCountdownStarted);
|
||||
msg.WriteSingle(GameMain.Server.ServerSettings.MaxTransportTime);
|
||||
msg.WriteSingle((float)(ReturnTime - DateTime.Now).TotalSeconds);
|
||||
break;
|
||||
case State.Waiting:
|
||||
msg.WriteUInt16((ushort)pendingRespawnCount);
|
||||
msg.WriteUInt16((ushort)requiredRespawnCount);
|
||||
msg.WriteBoolean(IsRespawnDecisionPendingForClient(c));
|
||||
msg.WriteBoolean(RespawnCountdownStarted);
|
||||
msg.WriteSingle((float)(RespawnTime - DateTime.Now).TotalSeconds);
|
||||
break;
|
||||
case State.Returning:
|
||||
break;
|
||||
msg.WriteByte((byte)teamSpecificState.TeamID);
|
||||
msg.WriteRangedInteger((int)teamSpecificState.CurrentState, 0, Enum.GetNames(typeof(State)).Length);
|
||||
|
||||
switch (teamSpecificState.CurrentState)
|
||||
{
|
||||
case State.Transporting:
|
||||
msg.WriteBoolean(teamSpecificState.ReturnCountdownStarted);
|
||||
msg.WriteSingle(GameMain.Server.ServerSettings.MaxTransportTime);
|
||||
msg.WriteSingle((float)(teamSpecificState.ReturnTime - DateTime.Now).TotalSeconds);
|
||||
break;
|
||||
case State.Waiting:
|
||||
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;
|
||||
case State.Returning:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
msg.WritePadBits();
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
using Barotrauma.IO;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Steam;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
@@ -74,7 +74,7 @@ namespace Barotrauma.Networking
|
||||
{
|
||||
var property = netProperties[key];
|
||||
property.SyncValue();
|
||||
if (NetIdUtils.IdMoreRecent(property.LastUpdateID, c.LastRecvLobbyUpdate))
|
||||
if (NetIdUtils.IdMoreRecent(property.LastUpdateID, c.LastRecvLobbyUpdate) || !c.InitialLobbyUpdateSent)
|
||||
{
|
||||
outMsg.WriteUInt32(key);
|
||||
netProperties[key].Write(outMsg);
|
||||
@@ -106,6 +106,7 @@ namespace Barotrauma.Networking
|
||||
if (requiredFlags.HasFlag(NetFlags.Properties))
|
||||
{
|
||||
WriteExtraCargo(outMsg);
|
||||
WritePerks(outMsg);
|
||||
}
|
||||
|
||||
if (requiredFlags.HasFlag(NetFlags.HiddenSubs))
|
||||
@@ -129,6 +130,40 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
public void ReadPerks(IReadMessage incMsg, Client c)
|
||||
{
|
||||
if (!HasPermissionToChangePerks(c)) return;
|
||||
|
||||
bool changed = ReadPerks(incMsg);
|
||||
if (!changed) { return; }
|
||||
|
||||
UpdateFlag(NetFlags.Properties);
|
||||
SaveSettings();
|
||||
GameMain.NetLobbyScreen.LastUpdateID++;
|
||||
|
||||
static bool HasPermissionToChangePerks(Client client)
|
||||
{
|
||||
if (client.HasPermission(Networking.ClientPermissions.ManageSettings)) { return true; }
|
||||
|
||||
bool isPvP = GameMain.NetLobbyScreen?.SelectedMode == GameModePreset.PvP;
|
||||
bool hasSelectedTeam = client.PreferredTeam is CharacterTeamType.Team1 or CharacterTeamType.Team2;
|
||||
var otherClients = GameMain.NetworkMember?.ConnectedClients?.Where(c => c != client).ToImmutableArray() ?? ImmutableArray<Client>.Empty;
|
||||
|
||||
if (isPvP)
|
||||
{
|
||||
if (!hasSelectedTeam) { return false; }
|
||||
|
||||
return !otherClients
|
||||
.Where(c => c.PreferredTeam == client.PreferredTeam)
|
||||
.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
|
||||
}
|
||||
else
|
||||
{
|
||||
return !otherClients.Any(static c => c.HasPermission(Networking.ClientPermissions.ManageSettings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ServerRead(IReadMessage incMsg, Client c)
|
||||
{
|
||||
if (!c.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
|
||||
@@ -136,7 +171,7 @@ namespace Barotrauma.Networking
|
||||
NetFlags flags = (NetFlags)incMsg.ReadByte();
|
||||
|
||||
bool changed = false;
|
||||
|
||||
|
||||
if (flags.HasFlag(NetFlags.Properties))
|
||||
{
|
||||
bool propertiesChanged = ReadExtraCargo(incMsg);
|
||||
@@ -176,6 +211,7 @@ namespace Barotrauma.Networking
|
||||
if (propertiesChanged)
|
||||
{
|
||||
UpdateFlag(NetFlags.Properties);
|
||||
GameMain.Server.RefreshPvpTeamAssignments(); // the changed settings might be relevant to team logic, so refresh
|
||||
}
|
||||
changed |= propertiesChanged;
|
||||
}
|
||||
@@ -189,9 +225,18 @@ namespace Barotrauma.Networking
|
||||
|
||||
if (flags.HasFlag(NetFlags.Misc))
|
||||
{
|
||||
int orBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
int andBits = incMsg.ReadRangedInteger(0, (int)Barotrauma.MissionType.All) & (int)Barotrauma.MissionType.All;
|
||||
GameMain.NetLobbyScreen.MissionType = (MissionType)(((int)GameMain.NetLobbyScreen.MissionType | orBits) & andBits);
|
||||
List<Identifier> missionTypes = new List<Identifier>(GameMain.NetLobbyScreen.MissionTypes);
|
||||
Identifier addedMissionType = incMsg.ReadIdentifier();
|
||||
Identifier removedMissionType = incMsg.ReadIdentifier();
|
||||
if (!addedMissionType.IsEmpty)
|
||||
{
|
||||
missionTypes.Add(addedMissionType);
|
||||
}
|
||||
if (!removedMissionType.IsEmpty)
|
||||
{
|
||||
missionTypes.Remove(removedMissionType);
|
||||
}
|
||||
GameMain.NetLobbyScreen.MissionTypes = missionTypes;
|
||||
|
||||
//the byte indicates the direction we're changing the value, subtract one to get negative values from a byte
|
||||
TraitorDangerLevel = TraitorDangerLevel + incMsg.ReadByte() - 1;
|
||||
@@ -223,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)
|
||||
@@ -235,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));
|
||||
@@ -280,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;
|
||||
@@ -361,28 +408,16 @@ namespace Barotrauma.Networking
|
||||
if (min > -1 && max > -1) { AllowedClientNameChars.Add(new Range<int>(min, max)); }
|
||||
}
|
||||
|
||||
AllowedRandomMissionTypes = new List<MissionType>();
|
||||
string[] allowedMissionTypeNames = doc.Root.GetAttributeStringArray(
|
||||
"AllowedRandomMissionTypes", Enum.GetValues(typeof(MissionType)).Cast<MissionType>().Select(m => m.ToString()).ToArray());
|
||||
foreach (string missionTypeName in allowedMissionTypeNames)
|
||||
{
|
||||
if (Enum.TryParse(missionTypeName, out MissionType missionType))
|
||||
{
|
||||
if (missionType == Barotrauma.MissionType.None) { continue; }
|
||||
if (MissionPrefab.HiddenMissionClasses.Contains(missionType)) { continue; }
|
||||
AllowedRandomMissionTypes.Add(missionType);
|
||||
}
|
||||
}
|
||||
|
||||
ServerName = doc.Root.GetAttributeString("name", "");
|
||||
if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
|
||||
ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
|
||||
AllowedRandomMissionTypes = doc.Root.GetAttributeIdentifierArray(
|
||||
"AllowedRandomMissionTypes", MissionPrefab.GetAllMultiplayerSelectableMissionTypes().ToArray()).ToList();
|
||||
|
||||
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;
|
||||
//handle Random as the mission type, which is no longer a valid setting
|
||||
//MissionType.All offers equivalent functionality
|
||||
if (MissionType == "Random") { MissionType = "All"; }
|
||||
GameMain.NetLobbyScreen.MissionTypeName = MissionType;
|
||||
if (AllowedRandomMissionTypes.Contains(Tags.MissionTypeAll))
|
||||
{
|
||||
AllowedRandomMissionTypes = MissionPrefab.GetAllMultiplayerSelectableMissionTypes().ToList();
|
||||
}
|
||||
AllowedRandomMissionTypes = AllowedRandomMissionTypes.Distinct().ToList();
|
||||
ValidateMissionTypes();
|
||||
|
||||
GameMain.NetLobbyScreen.SetBotSpawnMode(BotSpawnMode);
|
||||
GameMain.NetLobbyScreen.SetBotCount(BotCount);
|
||||
@@ -404,6 +439,20 @@ namespace Barotrauma.Networking
|
||||
CampaignSettings = new CampaignSettings(element);
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Identifier> selectedCoalitionPerks = SelectedCoalitionPerks.ToHashSet();
|
||||
HashSet<Identifier> selectedSeparatistsPerks = SelectedSeparatistsPerks.ToHashSet();
|
||||
foreach (DisembarkPerkPrefab prefab in DisembarkPerkPrefab.Prefabs)
|
||||
{
|
||||
if (prefab.Cost == 0)
|
||||
{
|
||||
selectedSeparatistsPerks.Add(prefab.Identifier);
|
||||
selectedCoalitionPerks.Add(prefab.Identifier);
|
||||
}
|
||||
}
|
||||
|
||||
SelectedCoalitionPerks = selectedCoalitionPerks.ToArray();
|
||||
SelectedSeparatistsPerks = selectedSeparatistsPerks.ToArray();
|
||||
}
|
||||
|
||||
public string SelectNonHiddenSubmarine(string current = null)
|
||||
|
||||
@@ -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))
|
||||
@@ -374,13 +384,32 @@ namespace Barotrauma
|
||||
msg.WriteBoolean(GameMain.Server.ServerSettings.AllowSubVoting);
|
||||
if (GameMain.Server.ServerSettings.AllowSubVoting)
|
||||
{
|
||||
IReadOnlyDictionary<SubmarineInfo, int> voteList = GetVoteCounts<SubmarineInfo>(VoteType.Sub, GameMain.Server.ConnectedClients);
|
||||
bool isMultiSub = GameMain.NetLobbyScreen.SelectedMode == GameModePreset.PvP;
|
||||
msg.WriteBoolean(isMultiSub);
|
||||
|
||||
var subVoters = isMultiSub ?
|
||||
GameMain.Server.ConnectedClients.Where(static c => c.PreferredTeam is CharacterTeamType.Team1) :
|
||||
GameMain.Server.ConnectedClients;
|
||||
|
||||
IReadOnlyDictionary<SubmarineInfo, int> voteList = GetVoteCounts<SubmarineInfo>(VoteType.Sub, subVoters);
|
||||
msg.WriteByte((byte)voteList.Count);
|
||||
foreach (KeyValuePair<SubmarineInfo, int> vote in voteList)
|
||||
{
|
||||
msg.WriteByte((byte)vote.Value);
|
||||
msg.WriteString(vote.Key.Name);
|
||||
}
|
||||
|
||||
if (isMultiSub)
|
||||
{
|
||||
var separatistsVotes = GetVoteCounts<SubmarineInfo>(VoteType.Sub, GameMain.Server.ConnectedClients.Where(static c => c.PreferredTeam is CharacterTeamType.Team2));
|
||||
msg.WriteByte((byte)separatistsVotes.Count);
|
||||
|
||||
foreach (var (info, amount) in separatistsVotes)
|
||||
{
|
||||
msg.WriteByte((byte)amount);
|
||||
msg.WriteString(info.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.WriteBoolean(GameMain.Server.ServerSettings.AllowModeVoting);
|
||||
if (GameMain.Server.ServerSettings.AllowModeVoting)
|
||||
@@ -396,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,7 +1,9 @@
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -9,6 +11,7 @@ namespace Barotrauma
|
||||
partial class NetLobbyScreen : Screen
|
||||
{
|
||||
private SubmarineInfo selectedSub;
|
||||
private SubmarineInfo selectedEnemySub;
|
||||
private SubmarineInfo selectedShuttle;
|
||||
|
||||
public bool RadiationEnabled = true;
|
||||
@@ -26,6 +29,18 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MaybeNull, AllowNull]
|
||||
public SubmarineInfo SelectedEnemySub
|
||||
{
|
||||
get => selectedEnemySub;
|
||||
set
|
||||
{
|
||||
selectedEnemySub = value;
|
||||
lastUpdateID++;
|
||||
}
|
||||
}
|
||||
|
||||
public SubmarineInfo SelectedShuttle
|
||||
{
|
||||
get { return selectedShuttle; }
|
||||
@@ -81,31 +96,19 @@ namespace Barotrauma
|
||||
get { return GameModes[SelectedModeIndex]; }
|
||||
}
|
||||
|
||||
private MissionType missionType;
|
||||
public MissionType MissionType
|
||||
public IEnumerable<Identifier> MissionTypes
|
||||
{
|
||||
get { return missionType; }
|
||||
get { return GameMain.NetworkMember.ServerSettings.AllowedRandomMissionTypes; }
|
||||
set
|
||||
{
|
||||
lastUpdateID++;
|
||||
missionType = value;
|
||||
if (GameMain.NetworkMember?.ServerSettings != null)
|
||||
{
|
||||
GameMain.NetworkMember.ServerSettings.MissionType = missionType.ToString();
|
||||
GameMain.NetworkMember.ServerSettings.MissionTypes = string.Join(",", value.Select(t => t.ToIdentifier()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string MissionTypeName
|
||||
{
|
||||
get { return missionType.ToString(); }
|
||||
set
|
||||
{
|
||||
Enum.TryParse(value, out MissionType type);
|
||||
MissionType = type;
|
||||
}
|
||||
}
|
||||
|
||||
public NetLobbyScreen()
|
||||
{
|
||||
LevelSeed = ToolBox.RandomSeed(8);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma.Steam
|
||||
@@ -63,9 +65,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}",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#nullable enable
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
@@ -273,8 +273,11 @@ namespace Barotrauma
|
||||
for (int i = 0; i < amountToChoose; i++)
|
||||
{
|
||||
var traitor = viableTraitors.GetRandomUnsynced();
|
||||
viableTraitors.Remove(traitor);
|
||||
traitors.Add(traitor);
|
||||
if (traitor != null)
|
||||
{
|
||||
viableTraitors.Remove(traitor);
|
||||
traitors.Add(traitor);
|
||||
}
|
||||
}
|
||||
return traitors;
|
||||
}
|
||||
@@ -388,7 +391,7 @@ namespace Barotrauma
|
||||
{
|
||||
if (level?.LevelData is { Type: LevelData.LevelType.LocationConnection })
|
||||
{
|
||||
if (Submarine.MainSub.WorldPosition.X > level.Size.X / 2)
|
||||
if (Submarine.MainSub != null && Submarine.MainSub.WorldPosition.X > level.Size.X / 2)
|
||||
{
|
||||
//try starting ASAP if the submarine is already half-way through the level
|
||||
//(brief delay regardless, because otherwise we might retry every frame if finding a suitable event fails below)
|
||||
|
||||
Reference in New Issue
Block a user