Unstable 1.1.14.0
This commit is contained in:
@@ -1,207 +1,505 @@
|
||||
// #define DISABLE_MISSIONS
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TraitorManager
|
||||
{
|
||||
public static readonly Random Random = new Random((int)DateTime.UtcNow.Ticks);
|
||||
sealed partial class TraitorManager
|
||||
{
|
||||
const int MaxPreviousEventHistory = 10;
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static int RandomInt(int n) => Random.Next(n);
|
||||
const int StartDelayMin = 60;
|
||||
const int StartDelayMax = 200;
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static double RandomDouble() => Random.NextDouble();
|
||||
private float startTimer;
|
||||
private bool started = false;
|
||||
|
||||
public readonly Dictionary<CharacterTeamType, Traitor.TraitorMission> Missions = new Dictionary<CharacterTeamType, Traitor.TraitorMission>();
|
||||
private TraitorResults? results = null;
|
||||
|
||||
public string GetCodeWords(CharacterTeamType team) => Missions.TryGetValue(team, out var mission) ? mission.CodeWords : "";
|
||||
public string GetCodeResponse(CharacterTeamType team) => Missions.TryGetValue(team, out var mission) ? mission.CodeResponse : "";
|
||||
|
||||
public IEnumerable<Traitor> Traitors => Missions.Values.SelectMany(mission => mission.Traitors.Values);
|
||||
|
||||
private float startCountdown = 0.0f;
|
||||
private GameServer server;
|
||||
|
||||
public bool ShouldEndRound
|
||||
public class PreviousTraitorEvent
|
||||
{
|
||||
get;
|
||||
set;
|
||||
public TraitorEventPrefab TraitorEvent { get; }
|
||||
public TraitorEvent.State State { get; }
|
||||
|
||||
public Client Traitor =>
|
||||
GameMain.Server.ConnectedClients.Find(c => traitorAccountId.IsSome() && traitorAccountId == c.AccountId) ??
|
||||
GameMain.Server.ConnectedClients.Find(c => traitorAddress == c.Connection.Endpoint.Address);
|
||||
|
||||
private readonly Address traitorAddress;
|
||||
private readonly Option<AccountId> traitorAccountId;
|
||||
|
||||
public PreviousTraitorEvent(TraitorEventPrefab traitorEvent, TraitorEvent.State state, Client traitor)
|
||||
{
|
||||
TraitorEvent = traitorEvent;
|
||||
State = state;
|
||||
traitorAddress = traitor.Connection.Endpoint.Address;
|
||||
traitorAccountId = traitor.AccountId;
|
||||
}
|
||||
|
||||
private PreviousTraitorEvent(TraitorEventPrefab traitorEvent, TraitorEvent.State state, Option<AccountId> accountId, Address address)
|
||||
{
|
||||
TraitorEvent = traitorEvent;
|
||||
State = state;
|
||||
traitorAddress = address;
|
||||
traitorAccountId = accountId;
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
{
|
||||
parentElement.Add(
|
||||
new XElement(nameof(PreviousTraitorEvent),
|
||||
new XAttribute("id", TraitorEvent.Identifier),
|
||||
new XAttribute("state", State),
|
||||
new XAttribute("accountid", traitorAccountId),
|
||||
new XAttribute("address", traitorAddress)));
|
||||
}
|
||||
|
||||
public static PreviousTraitorEvent? Load(XElement subElement)
|
||||
{
|
||||
var traitorEventId = subElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
var state = subElement.GetAttributeEnum("state", Barotrauma.TraitorEvent.State.Failed);
|
||||
var accountId = Networking.AccountId.Parse(
|
||||
subElement.GetAttributeString("accountid", null)
|
||||
?? subElement.GetAttributeString("steamid", ""));
|
||||
var address = Address.Parse(
|
||||
subElement.GetAttributeString("address", null)
|
||||
?? subElement.GetAttributeString("endpoint", ""))
|
||||
.Fallback(new UnknownAddress());
|
||||
if (EventPrefab.Prefabs.TryGet(traitorEventId, out EventPrefab? prefab) && prefab is TraitorEventPrefab traitorEventPrefab)
|
||||
{
|
||||
return new PreviousTraitorEvent(traitorEventPrefab, state, accountId, address);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Error when loading {nameof(TraitorManager)}: could not find a traitor event prefab with the identifier \"{traitorEventId}\".");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly record struct ActiveTraitorEvent(
|
||||
Client Traitor,
|
||||
TraitorEvent TraitorEvent);
|
||||
|
||||
private readonly List<PreviousTraitorEvent> previousTraitorEvents = new List<PreviousTraitorEvent>();
|
||||
|
||||
private readonly List<ActiveTraitorEvent> activeEvents = new List<ActiveTraitorEvent>();
|
||||
|
||||
public IEnumerable<ActiveTraitorEvent> ActiveEvents => activeEvents;
|
||||
|
||||
private readonly GameServer server;
|
||||
|
||||
private EventManager? eventManager;
|
||||
private Level? level;
|
||||
|
||||
public bool Enabled;
|
||||
|
||||
public bool IsTraitor(Character character)
|
||||
{
|
||||
if (Traitors == null)
|
||||
return activeEvents.Any(e => e.Traitor.Character == character);
|
||||
}
|
||||
|
||||
public TraitorManager(GameServer server)
|
||||
{
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public void Initialize(EventManager eventManager, Level level)
|
||||
{
|
||||
this.eventManager = eventManager;
|
||||
this.level = level;
|
||||
startTimer = Rand.Range(StartDelayMin, StartDelayMax);
|
||||
started = false;
|
||||
results = null;
|
||||
}
|
||||
|
||||
private bool TryCreateTraitorEvents(EventManager eventManager, Level level)
|
||||
{
|
||||
var eventPrefabs = EventPrefab.Prefabs.Where(p => p is TraitorEventPrefab).Cast<TraitorEventPrefab>();
|
||||
if (!eventPrefabs.Any())
|
||||
{
|
||||
DebugConsole.AddWarning("No traitor event available in any of the enabled content packages.");
|
||||
return false;
|
||||
}
|
||||
return Traitors.Any(traitor => traitor.Character == character);
|
||||
|
||||
if (server.ConnectedClients.Count(IsClientViableTraitor) < server.ServerSettings.TraitorsMinPlayerCount)
|
||||
{
|
||||
DebugConsole.AddWarning("Not enough clients to create a traitor event. Active traitor events: " + activeEvents.Count);
|
||||
#if DEBUG
|
||||
DebugConsole.AddWarning("Starting a traitor event anyway because this is a debug build.");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
int maxDangerLevel = server.ServerSettings.TraitorDangerLevel;
|
||||
|
||||
int playerCount = server.ConnectedClients.Count(c => c.Character != null && !c.Character.Removed);
|
||||
|
||||
var campaign = GameMain.GameSession?.Campaign;
|
||||
var suitablePrefabs = eventPrefabs
|
||||
.Where(e => EventManager.IsLevelSuitable(e, level))
|
||||
.Where(e => e.ReputationRequirementsMet(campaign))
|
||||
.Where(e => e.MissionRequirementsMet(GameMain.GameSession))
|
||||
.Where(e => e.LevelRequirementsMet(level))
|
||||
.Where(e => e.DangerLevel <= maxDangerLevel)
|
||||
.Where(e => playerCount >= e.MinPlayerCount)
|
||||
.ToList();
|
||||
|
||||
var minDangerLevelPrefabs = suitablePrefabs.FindAll(e => e.DangerLevel == TraitorEventPrefab.MinDangerLevel);
|
||||
|
||||
if (!suitablePrefabs.Any())
|
||||
{
|
||||
//this is normal, there e.g. might be no missions for an abandoned outpost or end level
|
||||
DebugConsole.Log("No suitable traitor missions available for the level.");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var previousEvent in previousTraitorEvents.DistinctBy(e => e.Traitor).Reverse())
|
||||
{
|
||||
if (previousEvent.State == TraitorEvent.State.Completed &&
|
||||
previousEvent.TraitorEvent.DangerLevel < maxDangerLevel &&
|
||||
previousEvent.TraitorEvent.IsChainable &&
|
||||
IsClientViableTraitor(previousEvent.Traitor))
|
||||
{
|
||||
GameServer.Log($"{NetworkMember.ClientLogName(previousEvent.Traitor)} successfully completed a traitor event on a previous round. Attempting to give choose them a new, more dangerous event...", ServerLog.MessageType.Traitors);
|
||||
|
||||
var suitablePrefab =
|
||||
//try finding an event that's continuation from the previous one (= requires the previous one to be completed 1st)
|
||||
suitablePrefabs.GetRandomUnsynced(p => p.RequiredCompletedTags.Any(t =>
|
||||
previousEvent.TraitorEvent.Identifier == t || previousEvent.TraitorEvent.Tags.Contains(t)));
|
||||
|
||||
suitablePrefab ??=
|
||||
//otherwise try finding some higher-difficult event for the same faction
|
||||
suitablePrefabs.GetRandomUnsynced(p =>
|
||||
p.RequiredCompletedTags.None() &&
|
||||
p.DangerLevel > previousEvent.TraitorEvent.DangerLevel &&
|
||||
p.Faction == previousEvent.TraitorEvent.Faction);
|
||||
|
||||
if (suitablePrefab != null)
|
||||
{
|
||||
CreateTraitorEvent(eventManager, suitablePrefab, previousEvent.Traitor);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
GameServer.Log($"Could not find a suitable, more difficult traitor event for {NetworkMember.ClientLogName(previousEvent.Traitor)}.", ServerLog.MessageType.Traitors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TraitorEventPrefab selectedPrefab;
|
||||
if (GameMain.GameSession?.Campaign == null)
|
||||
{
|
||||
selectedPrefab = suitablePrefabs.GetRandomByWeight(GetTraitorEventPrefabCommonness, Rand.RandSync.Unsynced);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedPrefab = minDangerLevelPrefabs.GetRandomByWeight(GetTraitorEventPrefabCommonness, Rand.RandSync.Unsynced);
|
||||
if (selectedPrefab == null)
|
||||
{
|
||||
GameServer.Log($"Could not find a suitable danger level {TraitorEventPrefab.MinDangerLevel} traitor event. Choosing a random event instead.", ServerLog.MessageType.Traitors);
|
||||
selectedPrefab = suitablePrefabs.GetRandomByWeight(GetTraitorEventPrefabCommonness, Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedPrefab != null)
|
||||
{
|
||||
var selectedTraitor = SelectRandomTraitor();
|
||||
if (selectedTraitor == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{selectedPrefab.Identifier}\".");
|
||||
return false;
|
||||
}
|
||||
CreateTraitorEvent(eventManager, selectedPrefab, selectedTraitor);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public string GetTraitorRole(Character character)
|
||||
private Client? SelectRandomTraitor()
|
||||
{
|
||||
var traitor = Traitors.FirstOrDefault(candidate => candidate.Character == character);
|
||||
if (GameSettings.CurrentConfig.VerboseLogging)
|
||||
{
|
||||
GameServer.Log(
|
||||
$"Choosing a random traitor... Available traitors:"
|
||||
+ string.Concat(server.ConnectedClients.Where(IsClientViableTraitor).Select(c => $"\n - {c.Name} ({(int)(GetTraitorProbability(c) * 100)}%)")),
|
||||
ServerLog.MessageType.Traitors);
|
||||
}
|
||||
return server.ConnectedClients.Where(IsClientViableTraitor).GetRandomByWeight(GetTraitorProbability, Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
private IEnumerable<Client> SelectSecondaryTraitors(TraitorEvent traitorEvent, Client mainTraitor)
|
||||
{
|
||||
if (traitorEvent.Prefab.SecondaryTraitorPercentage <= 0.0f &&
|
||||
traitorEvent.Prefab.SecondaryTraitorAmount <= 0)
|
||||
{
|
||||
return Enumerable.Empty<Client>();
|
||||
}
|
||||
|
||||
var viableTraitors = server.ConnectedClients.Where(c => c != mainTraitor && IsClientViableTraitor(c)).ToList();
|
||||
|
||||
int amountToChoose = (int)Math.Ceiling(viableTraitors.Count * (traitorEvent.Prefab.SecondaryTraitorPercentage / 100.0f));
|
||||
amountToChoose = Math.Max(amountToChoose, traitorEvent.Prefab.SecondaryTraitorAmount);
|
||||
|
||||
if (amountToChoose > viableTraitors.Count)
|
||||
{
|
||||
DebugConsole.ThrowError(
|
||||
$"Error in traitor event {traitorEvent.Prefab.Identifier}. Not enough players to choose {amountToChoose} secondary traitors."+
|
||||
$"Make sure the {nameof(traitorEvent.Prefab.MinPlayerCount)} of the event is high enough to support to desired amount of secondary traitors.");
|
||||
amountToChoose = viableTraitors.Count;
|
||||
}
|
||||
|
||||
List<Client> traitors = new List<Client>();
|
||||
for (int i = 0; i < amountToChoose; i++)
|
||||
{
|
||||
var traitor = viableTraitors.GetRandomUnsynced();
|
||||
viableTraitors.Remove(traitor);
|
||||
traitors.Add(traitor);
|
||||
}
|
||||
return traitors;
|
||||
}
|
||||
|
||||
private bool IsClientViableTraitor(Client client)
|
||||
{
|
||||
return
|
||||
client != null &&
|
||||
server.ConnectedClients.Contains(client) &&
|
||||
client.Character != null &&
|
||||
!client.Character.IsIncapacitated && !client.Character.Removed &&
|
||||
activeEvents.None(e => e.Traitor == client);
|
||||
}
|
||||
|
||||
private float GetTraitorEventPrefabCommonness(TraitorEventPrefab prefab)
|
||||
{
|
||||
int? roundsSinceLastSelected = GetRoundsSinceLastSelected(e => e.TraitorEvent == prefab);
|
||||
if (roundsSinceLastSelected.HasValue)
|
||||
{
|
||||
float normalizedRoundsSinceLastSelected = MathUtils.InverseLerp(0, MaxPreviousEventHistory, roundsSinceLastSelected.Value);
|
||||
//exponentially decreasing commonness the closer the last time the event was selected
|
||||
return prefab.Commonness * normalizedRoundsSinceLastSelected * normalizedRoundsSinceLastSelected;
|
||||
}
|
||||
else
|
||||
{
|
||||
return prefab.Commonness;
|
||||
}
|
||||
}
|
||||
|
||||
private float GetTraitorProbability(Client client)
|
||||
{
|
||||
int? roundsSinceLastSelected = GetRoundsSinceLastSelected(e => e.Traitor == client);
|
||||
if (roundsSinceLastSelected.HasValue)
|
||||
{
|
||||
float normalizedRoundsSinceLastSelected = MathUtils.InverseLerp(0, MaxPreviousEventHistory, roundsSinceLastSelected.Value);
|
||||
//exponentially decreasing commonness the closer the last time the event was selected
|
||||
return normalizedRoundsSinceLastSelected * normalizedRoundsSinceLastSelected;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
private int? GetRoundsSinceLastSelected(Func<PreviousTraitorEvent, bool> condition)
|
||||
{
|
||||
//most recent events are at the end of the list, start from there
|
||||
for (int i = previousTraitorEvents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (condition(previousTraitorEvents[i]))
|
||||
{
|
||||
return previousTraitorEvents.Count - i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void CreateTraitorEvent(EventManager eventManager, TraitorEventPrefab selectedPrefab, Client traitor)
|
||||
{
|
||||
if (selectedPrefab.TryCreateInstance<TraitorEvent>(out var newEvent))
|
||||
{
|
||||
var secondaryTraitors = SelectSecondaryTraitors(newEvent, traitor);
|
||||
|
||||
string logMessage = $"{NetworkMember.ClientLogName(traitor)} was selected as a traitor. Selected event: {selectedPrefab.Name}";
|
||||
if (secondaryTraitors.Any())
|
||||
{
|
||||
logMessage += $", secondary traitors: {string.Join(", ", secondaryTraitors.Select(c => NetworkMember.ClientLogName(c)))}";
|
||||
}
|
||||
GameServer.Log(logMessage, ServerLog.MessageType.Traitors);
|
||||
|
||||
newEvent.OnStateChanged += () => SendCurrentState(newEvent);
|
||||
activeEvents.Add(new ActiveTraitorEvent(traitor, newEvent));
|
||||
newEvent.SetTraitor(traitor);
|
||||
newEvent.SetSecondaryTraitors(secondaryTraitors);
|
||||
eventManager.ActivateEvent(newEvent);
|
||||
SendCurrentState(newEvent);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to create an instance of the traitor event prefab \"{selectedPrefab.Identifier}\"!");
|
||||
}
|
||||
}
|
||||
|
||||
public void ForceTraitorEvent(TraitorEventPrefab traitorEventPrefab)
|
||||
{
|
||||
if (eventManager == null)
|
||||
{
|
||||
throw new InvalidOperationException("EventManager was null. TraitorManager may not have been initialized properly.");
|
||||
}
|
||||
var traitor = SelectRandomTraitor();
|
||||
if (traitor == null)
|
||||
{
|
||||
return "";
|
||||
DebugConsole.ThrowError($"Could not find a suitable traitor for the event \"{traitorEventPrefab.Identifier}\".");
|
||||
return;
|
||||
}
|
||||
return traitor.Role;
|
||||
}
|
||||
|
||||
public TraitorManager()
|
||||
{
|
||||
}
|
||||
|
||||
public void Start(GameServer server)
|
||||
{
|
||||
#if DISABLE_MISSIONS
|
||||
return;
|
||||
#endif
|
||||
if (server == null) { return; }
|
||||
|
||||
ShouldEndRound = false;
|
||||
|
||||
this.server = server;
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinStartDelay, server.ServerSettings.TraitorsMaxStartDelay, (float)RandomDouble());
|
||||
CreateTraitorEvent(eventManager, traitorEventPrefab, traitor);
|
||||
}
|
||||
|
||||
public void SkipStartDelay()
|
||||
{
|
||||
startCountdown = 0.01f;
|
||||
startTimer = 0.0f;
|
||||
if (activeEvents.Any()) { started = true; }
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
if (ShouldEndRound) { return; }
|
||||
|
||||
#if DISABLE_MISSIONS
|
||||
return;
|
||||
#endif
|
||||
if (Missions.Any())
|
||||
if (!Enabled) { return; }
|
||||
if (!started)
|
||||
{
|
||||
bool missionCompleted = false;
|
||||
bool gameShouldEnd = false;
|
||||
CharacterTeamType winningTeam = CharacterTeamType.None;
|
||||
foreach (var mission in Missions)
|
||||
if (level?.LevelData is { Type: LevelData.LevelType.LocationConnection })
|
||||
{
|
||||
mission.Value.Update(deltaTime, () =>
|
||||
if (Submarine.MainSub.WorldPosition.X > level.Size.X / 2)
|
||||
{
|
||||
switch (mission.Key)
|
||||
{
|
||||
case CharacterTeamType.Team1:
|
||||
winningTeam = (winningTeam == CharacterTeamType.None) ? CharacterTeamType.Team2 : CharacterTeamType.None;
|
||||
break;
|
||||
case CharacterTeamType.Team2:
|
||||
winningTeam = (winningTeam == CharacterTeamType.None) ? CharacterTeamType.Team1 : CharacterTeamType.None;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gameShouldEnd = true;
|
||||
});
|
||||
if (!gameShouldEnd && mission.Value.IsCompleted)
|
||||
{
|
||||
missionCompleted = true;
|
||||
foreach (var traitor in mission.Value.Traitors.Values)
|
||||
{
|
||||
traitor.UpdateCurrentObjective("", mission.Value.Identifier);
|
||||
}
|
||||
//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)
|
||||
startTimer = Math.Min(startTimer, 10.0f);
|
||||
}
|
||||
}
|
||||
if (gameShouldEnd)
|
||||
startTimer -= deltaTime;
|
||||
if (startTimer >= 0.0f) { return; }
|
||||
if (eventManager == null)
|
||||
{
|
||||
GameMain.GameSession.WinningTeam = winningTeam;
|
||||
ShouldEndRound = true;
|
||||
return;
|
||||
throw new InvalidOperationException("EventManager was null. TraitorManager may not have been initialized properly.");
|
||||
}
|
||||
if (missionCompleted)
|
||||
if (level == null)
|
||||
{
|
||||
Missions.Clear();
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)RandomDouble());
|
||||
throw new InvalidOperationException("Level was null. TraitorManager may not have been initialized properly.");
|
||||
}
|
||||
if (TryCreateTraitorEvents(eventManager, level))
|
||||
{
|
||||
started = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
//restart timer, we might be able to start a mission later if more clients join
|
||||
startTimer = Rand.Range(StartDelayMin, StartDelayMax);
|
||||
}
|
||||
}
|
||||
else if (startCountdown > 0.0f && server.GameStarted)
|
||||
}
|
||||
|
||||
public void EndRound()
|
||||
{
|
||||
Client? votedAsTraitor = GetClientAccusedAsTraitor();
|
||||
foreach (var activeEvent in activeEvents)
|
||||
{
|
||||
startCountdown -= deltaTime;
|
||||
if (startCountdown <= 0.0f)
|
||||
if (results != null)
|
||||
{
|
||||
int playerCharactersCount = server.ConnectedClients.Sum(client => client.Character != null && !client.Character.IsDead ? 1 : 0);
|
||||
if (playerCharactersCount < server.ServerSettings.TraitorsMinPlayerCount)
|
||||
DebugConsole.AddWarning("Multiple traitor events active during the round, only displaying the results for the last one.");
|
||||
}
|
||||
results = new TraitorResults(votedAsTraitor, activeEvent.TraitorEvent);
|
||||
if (results.Value.MoneyPenalty > 0)
|
||||
{
|
||||
GameMain.GameSession?.Campaign?.Bank?.TryDeduct(results.Value.MoneyPenalty);
|
||||
}
|
||||
|
||||
if (activeEvent.TraitorEvent.CurrentState != TraitorEvent.State.Completed)
|
||||
{
|
||||
activeEvent.TraitorEvent.CurrentState = TraitorEvent.State.Failed;
|
||||
}
|
||||
GameServer.Log(
|
||||
NetworkMember.ClientLogName(activeEvent.Traitor) +
|
||||
(activeEvent.TraitorEvent.CurrentState == TraitorEvent.State.Completed ?
|
||||
" completed their traitor objective successfully." : " failed to complete their traitor objective."),
|
||||
ServerLog.MessageType.Traitors);
|
||||
|
||||
//consider the event failed if the traitor was identifier correctly,
|
||||
//so the traitor doesn't get rewards or get assigned a follow-up event
|
||||
if (results.Value.VotedCorrectTraitor)
|
||||
{
|
||||
GameServer.Log(
|
||||
NetworkMember.ClientLogName(activeEvent.Traitor) + " was correctly identified as the traitor, and will not receive any rewards.",
|
||||
ServerLog.MessageType.Traitors);
|
||||
activeEvent.TraitorEvent.CurrentState = TraitorEvent.State.Failed;
|
||||
}
|
||||
previousTraitorEvents.Add(new PreviousTraitorEvent(
|
||||
activeEvent.TraitorEvent.Prefab,
|
||||
activeEvent.TraitorEvent.CurrentState,
|
||||
activeEvent.Traitor));
|
||||
}
|
||||
if (previousTraitorEvents.Count > MaxPreviousEventHistory)
|
||||
{
|
||||
previousTraitorEvents.RemoveRange(0, previousTraitorEvents.Count - MaxPreviousEventHistory);
|
||||
}
|
||||
activeEvents.Clear();
|
||||
}
|
||||
|
||||
public Client? GetClientAccusedAsTraitor()
|
||||
{
|
||||
Client? votedAsTraitor = Voting.HighestVoted<Client>(VoteType.Traitor, server.ConnectedClients.Where(c => c.Character is { IsDead: false }), out int voteCount);
|
||||
if (voteCount < server.ConnectedClients.Count * server.ServerSettings.MinPercentageOfPlayersForTraitorAccusation / 100.0f)
|
||||
{
|
||||
//at least x% of the players must've voted for the same player
|
||||
votedAsTraitor = null;
|
||||
}
|
||||
return votedAsTraitor;
|
||||
}
|
||||
|
||||
public TraitorResults? GetEndResults()
|
||||
{
|
||||
return results;
|
||||
}
|
||||
|
||||
public XElement Save()
|
||||
{
|
||||
var element = new XElement(nameof(TraitorManager),
|
||||
new XAttribute("version", GameMain.Version.ToString()));
|
||||
foreach (var previousEvent in previousTraitorEvents)
|
||||
{
|
||||
previousEvent.Save(element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
public void Load(XElement traitorManagerElement)
|
||||
{
|
||||
previousTraitorEvents.Clear();
|
||||
foreach (XElement subElement in traitorManagerElement.Elements())
|
||||
{
|
||||
if (subElement.Name.ToIdentifier() == nameof(PreviousTraitorEvent))
|
||||
{
|
||||
var previousTraitorEvent = PreviousTraitorEvent.Load(subElement);
|
||||
if (previousTraitorEvent != null)
|
||||
{
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)RandomDouble());
|
||||
return;
|
||||
previousTraitorEvents.Add(previousTraitorEvent);
|
||||
}
|
||||
if (Character.CharacterList.Count(c => !c.IsDead && c.TeamID == CharacterTeamType.Team1 || c.TeamID == CharacterTeamType.Team2) <= 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession.Missions.Any(m => m is CombatMission))
|
||||
{
|
||||
var teamIds = new[] { CharacterTeamType.Team1, CharacterTeamType.Team2 };
|
||||
foreach (var teamId in teamIds)
|
||||
{
|
||||
if (server.ConnectedClients.Count(c => c.Character != null && !c.Character.IsDead && c.TeamID == teamId) < 2)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
|
||||
if (mission != null)
|
||||
{
|
||||
Missions.Add(teamId, mission);
|
||||
}
|
||||
}
|
||||
var canBeStartedCount = Missions.Sum(mission => mission.Value.CanBeStarted(server, this, mission.Key) ? 1 : 0);
|
||||
if (canBeStartedCount >= Missions.Count)
|
||||
{
|
||||
var startSuccessCount = Missions.Sum(mission => mission.Value.Start(server, this, mission.Key) ? 1 : 0);
|
||||
if (startSuccessCount >= Missions.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
|
||||
if (mission != null)
|
||||
{
|
||||
if (mission.CanBeStarted(server, this, CharacterTeamType.None))
|
||||
{
|
||||
if (mission.Start(server, this, CharacterTeamType.None))
|
||||
{
|
||||
Missions.Add(CharacterTeamType.None, mission);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Missions.Clear();
|
||||
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)RandomDouble());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<TraitorMissionResult> GetEndResults()
|
||||
public void SendCurrentState(TraitorEvent ev)
|
||||
{
|
||||
List<TraitorMissionResult> results = new List<TraitorMissionResult>();
|
||||
|
||||
#if DISABLE_MISSIONS
|
||||
return results;
|
||||
#endif
|
||||
if (GameMain.Server == null || !Missions.Any()) { return results; }
|
||||
|
||||
foreach (var mission in Missions)
|
||||
{
|
||||
results.Add(new TraitorMissionResult(mission.Value));
|
||||
}
|
||||
|
||||
return results;
|
||||
if (ev?.Traitor == null) { return; }
|
||||
var msg = new WriteOnlyMessage();
|
||||
msg.WriteByte((byte)ServerPacketHeader.TRAITOR_MESSAGE);
|
||||
msg.WriteByte((byte)ev.CurrentState);
|
||||
msg.WriteIdentifier(ev.Prefab.Identifier);
|
||||
server.SendTraitorMessage(msg, ev.Traitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user