#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]; /// /// Lists of characters currently alive in the teams /// private List[] crews; /// /// List of all kills (of the characters in either team) during the round /// private readonly List kills = new List(); private float roundEndTimer; private float timeInTargetSubmarineTimer; public override LocalizedString Description { get { if (descriptions == null) { return ""; } //non-team-specific description return descriptions[0]; } } protected override void UpdateMissionSpecific(float deltaTime) { CheckTeamCharacters(); if (state == 0) { CheckWinCondition(deltaTime); for (int i = 0; i < teamDead.Length; i++) { if (!teamDead[i] && teamDead[1 - i]) { SetWinningTeam(i); break; } } } else { roundEndTimer -= deltaTime; if (roundEndTimer > 0.0f) { return; } if (teamDead[0] && teamDead[1]) { GameMain.GameSession.WinningTeam = CharacterTeamType.None; GameMain.Server?.EndGame(); } else if (GameMain.GameSession.WinningTeam != CharacterTeamType.None) { GameMain.Server.EndGame(); } } } 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(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 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 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)); } } } }