Files
LuaCsForBarotraumaEP/Barotrauma/BarotraumaServer/Source/Traitors/TraitorMission.cs
T
2019-10-24 18:06:23 +02:00

366 lines
17 KiB
C#
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//#define ALLOW_SOLO_TRAITOR
//#define ALLOW_NONHUMANOID_TRAITOR
using System;
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class Traitor
{
public class TraitorMission
{
private static System.Random random = null;
public static void InitializeRandom() => random = new System.Random((int)DateTime.UtcNow.Ticks);
// All traitor related functionality should use the following interface for generating random values
public static int Random(int n) => random.Next(n);
// All traitor related functionality should use the following interface for generating random values
public static double RandomDouble() => random.NextDouble();
private static string wordsTxt = Path.Combine("Content", "CodeWords.txt");
private readonly List<Objective> allObjectives = new List<Objective>();
private readonly List<Objective> pendingObjectives = new List<Objective>();
private readonly List<Objective> completedObjectives = new List<Objective>();
public virtual bool IsCompleted => pendingObjectives.Count <= 0;
public readonly Dictionary<string, Traitor> Traitors = new Dictionary<string, Traitor>();
public delegate bool RoleFilter(Character character);
public readonly Dictionary<string, RoleFilter> Roles = new Dictionary<string, RoleFilter>();
public string StartText { get; private set; }
public string CodeWords { get; private set; }
public string CodeResponse { get; private set; }
public string GlobalEndMessageSuccessTextId { get; private set; }
public string GlobalEndMessageSuccessDeadTextId { get; private set; }
public string GlobalEndMessageSuccessDetainedTextId { get; private set; }
public string GlobalEndMessageFailureTextId { get; private set; }
public string GlobalEndMessageFailureDeadTextId { get; private set; }
public string GlobalEndMessageFailureDetainedTextId { get; private set; }
public readonly string Identifier;
public virtual IEnumerable<string> GlobalEndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
public virtual IEnumerable<string> GlobalEndMessageValues {
get {
var isSuccess = completedObjectives.Count >= allObjectives.Count;
return new string[] {
string.Join(", ", Traitors.Values.Select(traitor => traitor.Character?.Name ?? "(unknown)")),
(isSuccess ? completedObjectives.LastOrDefault() : pendingObjectives.FirstOrDefault())?.GoalInfos ?? ""
};
}
}
public string GlobalEndMessage
{
get
{
if (Traitors.Any() && allObjectives.Count > 0)
{
return TextManager.JoinServerMessages("\n",
Traitors.Values.Select(traitor =>
{
var isSuccess = completedObjectives.Count >= allObjectives.Count;
var traitorIsDead = traitor.Character.IsDead;
var traitorIsDetained = traitor.Character.LockHands;
var messageId = isSuccess
? (traitorIsDead ? GlobalEndMessageSuccessDeadTextId : traitorIsDetained ? GlobalEndMessageSuccessDetainedTextId : GlobalEndMessageSuccessTextId)
: (traitorIsDead ? GlobalEndMessageFailureDeadTextId : traitorIsDetained ? GlobalEndMessageFailureDetainedTextId : GlobalEndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(traitor.Character?.Info?.Gender ?? Gender.None, messageId, GlobalEndMessageKeys.ToArray(), GlobalEndMessageValues.ToArray());
}).ToArray());
}
return "";
}
}
public Objective GetCurrentObjective(Traitor traitor)
{
if (!Traitors.ContainsValue(traitor) || pendingObjectives.Count <= 0)
{
return null;
}
return pendingObjectives.Find(objective => objective.Roles.Contains(traitor.Role));
}
protected List<Tuple<Client, Character>> FindTraitorCandidates(GameServer server, Character.TeamType team, RoleFilter traitorRoleFilter)
{
var traitorCandidates = new List<Tuple<Client, Character>>();
foreach (Client c in server.ConnectedClients)
{
if (c.Character == null || c.Character.IsDead || c.Character.Removed || !traitorRoleFilter(c.Character) ||
(team != Character.TeamType.None && c.Character.TeamID != team))
{
continue;
}
#if !ALLOW_NONHUMANOID_TRAITOR
if (!c.Character.IsHumanoid) { continue; }
#endif
traitorCandidates.Add(Tuple.Create(c, c.Character));
}
return traitorCandidates;
}
protected List<Character> FindCharacters()
{
List<Character> characters = new List<Character>();
foreach (var character in Character.CharacterList)
{
characters.Add(character);
}
return characters;
}
protected List<Tuple<string, Tuple<Client, Character>>> AssignTraitors(GameServer server, TraitorManager traitorManager, Character.TeamType team)
{
List<Character> characters = FindCharacters();
#if !ALLOW_SOLO_TRAITOR
if (characters.Count < 2)
{
return null;
}
#endif
var roleCandidates = new Dictionary<string, HashSet<Tuple<Client, Character>>>();
foreach (var role in Roles)
{
roleCandidates.Add(role.Key, new HashSet<Tuple<Client, Character>>(FindTraitorCandidates(server, team, role.Value)));
if (roleCandidates[role.Key].Count <= 0)
{
return null;
}
}
var candidateRoleCounts = new Dictionary<Tuple<Client, Character>, int>();
foreach (var candidateEntry in roleCandidates)
{
foreach (var candidate in candidateEntry.Value)
{
candidateRoleCounts[candidate] = candidateRoleCounts.TryGetValue(candidate, out var count) ? count + 1 : 1;
}
}
var unassignedRoles = new List<string>(roleCandidates.Keys);
unassignedRoles.Sort((a, b) => roleCandidates[a].Count - roleCandidates[b].Count);
var assignedCandidates = new List<Tuple<string, Tuple<Client, Character>>>();
while (unassignedRoles.Count > 0)
{
var currentRole = unassignedRoles[0];
var availableCandidates = roleCandidates[currentRole].ToList();
if (availableCandidates.Count <= 0)
{
break;
}
unassignedRoles.RemoveAt(0);
availableCandidates.Sort((a, b) => candidateRoleCounts[b] - candidateRoleCounts[a]);
unassignedRoles.Sort((a, b) => roleCandidates[a].Count - roleCandidates[b].Count);
int numCandidates = 1;
for (int i = 1; i < availableCandidates.Count && candidateRoleCounts[availableCandidates[i]] == candidateRoleCounts[availableCandidates[0]]; ++i)
{
++numCandidates;
}
var selected = TraitorManager.WeightedRandom(availableCandidates, 0, numCandidates, Random, t =>
{
var previousClient = server.FindPreviousClientData(t.Item1);
return Math.Max(
previousClient != null ? traitorManager.GetTraitorCount(previousClient) : 0,
traitorManager.GetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? "")));
}, (t, c) => { traitorManager.SetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? ""), c); }, 2, 3);
assignedCandidates.Add(Tuple.Create(currentRole, selected));
foreach (var candidate in roleCandidates.Values)
{
candidate.Remove(selected);
}
}
if (unassignedRoles.Count > 0)
{
return null;
}
return assignedCandidates;
}
public virtual bool CanBeStarted(GameServer server, TraitorManager traitorManager, Character.TeamType team)
{
foreach (var role in Roles)
{
var candidates = FindTraitorCandidates(server, team, role.Value);
if (candidates.Count <= 0)
{
return false;
}
}
return AssignTraitors(server, traitorManager, team) != null;
}
public virtual bool Start(GameServer server, TraitorManager traitorManager, Character.TeamType team)
{
var assignedCandidates = AssignTraitors(server, traitorManager, team);
if (assignedCandidates == null)
{
return false;
}
Traitors.Clear();
foreach (var candidate in assignedCandidates)
{
var traitor = new Traitor(this, candidate.Item1, candidate.Item2.Item1.Character);
Traitors.Add(candidate.Item1, traitor);
}
CodeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
CodeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
if (pendingObjectives.Count <= 0 || !pendingObjectives[0].CanBeStarted(Traitors.Values))
{
Traitors.Clear();
return false;
}
var pendingMessages = new Dictionary<Traitor, List<string>>();
pendingMessages.Clear();
foreach (var traitor in Traitors.Values)
{
pendingMessages.Add(traitor, new List<string>());
}
foreach (var traitor in Traitors.Values)
{
traitor.Greet(server, CodeWords, CodeResponse, message => pendingMessages[traitor].Add(message));
}
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message, Identifier)));
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message, Identifier)));
Update(0.0f, () => { GameMain.Server.TraitorManager.ShouldEndRound = true; });
#if SERVER
foreach (var traitor in Traitors.Values)
{
GameServer.Log($"{traitor.Character.Name} is a traitor and the current goals are:\n{(traitor.CurrentObjective?.GoalInfos != null ? TextManager.GetServerMessage(traitor.CurrentObjective?.GoalInfos) : "(empty)")}", ServerLog.MessageType.ServerMessage);
}
#endif
return true;
}
public delegate void TraitorWinHandler();
public virtual void Update(float deltaTime, TraitorWinHandler winHandler)
{
if (pendingObjectives.Count <= 0 || Traitors.Count <= 0)
{
return;
}
if (Traitors.Values.Any(traitor => traitor.Character?.IsDead ?? true))
{
Traitors.Values.ForEach(traitor => traitor.UpdateCurrentObjective("", Identifier));
return;
}
var startedObjectives = new List<Objective>();
foreach (var traitor in Traitors.Values)
{
startedObjectives.Clear();
while (pendingObjectives.Count > 0)
{
var objective = GetCurrentObjective(traitor);
if (objective == null)
{
// No more objectives left for traitor or waiting for another traitor's objective.
break;
}
if (!objective.IsStarted)
{
if (!objective.Start(traitor))
{
//the mission fails if an objective cannot be started
if (completedObjectives.Count > 0)
{
objective.EndMessage();
}
pendingObjectives.Clear();
break;
}
startedObjectives.Add(objective);
}
objective.Update(deltaTime);
if (objective.IsCompleted)
{
pendingObjectives.Remove(objective);
completedObjectives.Add(objective);
if (pendingObjectives.Count > 0)
{
objective.EndMessage();
}
continue;
}
if (objective.IsStarted && !objective.CanBeCompleted)
{
objective.EndMessage();
pendingObjectives.Clear();
}
break;
}
if (pendingObjectives.Count > 0)
{
startedObjectives.ForEach(objective => objective.StartMessage());
}
}
if (completedObjectives.Count >= allObjectives.Count)
{
foreach (var traitor in Traitors)
{
SteamAchievementManager.OnTraitorWin(traitor.Value.Character);
}
winHandler();
}
}
public delegate bool CharacterFilter(Character character);
public Character FindKillTarget(Character traitor, CharacterFilter filter)
{
if (traitor == null) { return null; }
List<Character> validCharacters = Character.CharacterList.FindAll(c =>
c.TeamID == traitor.TeamID &&
c != traitor &&
!c.IsDead &&
(filter == null || filter(c)));
if (validCharacters.Count > 0)
{
return validCharacters[Random(validCharacters.Count)];
}
#if ALLOW_SOLO_TRAITOR
return traitor;
#else
return null;
#endif
}
public TraitorMission(string identifier, string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, IEnumerable<KeyValuePair<string, RoleFilter>> roles, ICollection<Objective> objectives)
{
Identifier = identifier;
StartText = startText;
GlobalEndMessageSuccessTextId = globalEndMessageSuccessTextId;
GlobalEndMessageSuccessDeadTextId = globalEndMessageSuccessDeadTextId;
GlobalEndMessageSuccessDetainedTextId = globalEndMessageSuccessDetainedTextId;
GlobalEndMessageFailureTextId = globalEndMessageFailureTextId;
GlobalEndMessageFailureDeadTextId = globalEndMessageFailureDeadTextId;
GlobalEndMessageFailureDetainedTextId = globalEndMessageFailureDetainedTextId;
foreach (var role in roles)
{
Roles.Add(role.Key, role.Value);
}
allObjectives.AddRange(objectives);
pendingObjectives.AddRange(objectives);
}
}
}
}