Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -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);
}
}