Unstable 1.1.14.0

This commit is contained in:
Markus Isberg
2023-10-02 16:43:54 +03:00
parent 94f5a93a0c
commit cf8f0de659
606 changed files with 21906 additions and 11456 deletions
@@ -1,64 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SqlServer.Server;
namespace Barotrauma
{
partial class Traitor
{
public abstract class Goal
{
public HashSet<Traitor> Traitors { get; } = new HashSet<Traitor>();
public TraitorMission Mission { get; internal set; }
public virtual string StatusTextId { get; set; } = "TraitorGoalStatusTextFormat";
public virtual string InfoTextId { get; set; } = null;
public virtual string CompletedTextId { get; set; } = null;
public virtual string StatusValueTextId => IsCompleted ? "complete" : "inprogress";
public virtual IEnumerable<string> StatusTextKeys => new [] { "[infotext]", "[status]" };
public virtual IEnumerable<string> StatusTextValues(Traitor traitor) => new [] { InfoText(traitor), TextManager.FormatServerMessage(StatusValueTextId) };
public virtual IEnumerable<string> InfoTextKeys => new string[] { };
public virtual IEnumerable<string> InfoTextValues(Traitor traitor) => new string[] { };
public virtual IEnumerable<string> CompletedTextKeys => new string[] { };
public virtual IEnumerable<string> CompletedTextValues(Traitor traitor) => new string[] { };
protected virtual string FormatText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
=> TextManager.FormatServerMessageWithPronouns(traitor.Character.Info, textId, keys.Zip(values, (k,v) => (k,v)).ToArray());
protected internal virtual string GetStatusText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
protected internal virtual string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
protected internal virtual string GetCompletedText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => FormatText(traitor, textId, keys, values);
public virtual string StatusText(Traitor traitor) => GetStatusText(traitor, StatusTextId, StatusTextKeys, StatusTextValues(traitor));
public virtual string InfoText(Traitor traitor) => GetInfoText(traitor, InfoTextId, InfoTextKeys, InfoTextValues(traitor));
public virtual string CompletedText(Traitor traitor) => CompletedTextId != null ? GetCompletedText(traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues(traitor)) : StatusText(traitor);
public abstract bool IsCompleted { get; }
public virtual bool IsStarted(Traitor traitor) => Traitors.Contains(traitor);
public virtual bool CanBeCompleted(ICollection<Traitor> traitors) => !Traitors.Any(traitor => traitor.Character?.IsDead ?? true);
public virtual bool IsEnemy(Character character) => false;
public virtual bool IsAllowedToDamage(Structure structure) => false;
public virtual bool Start(Traitor traitor)
{
Traitors.Add(traitor);
return true;
}
public virtual void Update(float deltaTime)
{
}
protected Goal()
{
}
}
}
}
@@ -1,107 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalDestroyItemsWithTag : Goal
{
private readonly string tag;
private readonly bool matchIdentifier;
private readonly bool matchTag;
private readonly bool matchInventory;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]", "[tag]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { string.Format("{0:0}", DestroyPercent * 100.0f), tagPrefabName ?? "" });
private readonly float destroyPercent;
private float DestroyPercent => destroyPercent;
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private int totalCount = 0;
private int targetCount = 0;
private string tagPrefabName = null;
private int CountMatchingItems()
{
int result = 0;
foreach (var item in Item.ItemList)
{
if (!matchInventory && Traitors.All(traitor => item.FindParentInventory(inventory => inventory.Owner is Character && inventory.Owner != traitor.Character) != null))
{
continue;
}
if (item.Submarine == null)
{
//items outside the sub don't count as destroyed if they're still in the traitor's inventory
bool carriedByTraitor = Traitors.Any(traitor => item.IsOwnedBy(traitor.Character));
if (!carriedByTraitor) { continue; }
}
else
{
if (Traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID)) { continue; }
}
if (item.Condition <= 0.0f)
{
continue;
}
var identifierMatches = matchIdentifier && ((MapEntity)item).Prefab.Identifier == tag;
if (identifierMatches && tagPrefabName == null)
{
var textId = item.Prefab.GetItemNameTextId();
tagPrefabName = textId != null ? TextManager.FormatServerMessage(textId) : item.Prefab.Name.Value;
}
if (identifierMatches || (matchTag && item.HasTag(tag)))
{
++result;
}
}
// Quick fix
if (tagPrefabName == null && matchIdentifier)
{
tagPrefabName = TextManager.FormatServerMessage($"entityname.{tag}");
}
return result;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = CountMatchingItems() <= targetCount;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
totalCount = CountMatchingItems();
if (totalCount <= 0)
{
return false;
}
targetCount = (int)((1.0f - destroyPercent) * totalCount - 0.5f);
return true;
}
public GoalDestroyItemsWithTag(string tag, float destroyPercent, bool matchTag, bool matchIdentifier, bool matchInventory) : base()
{
InfoTextId = "TraitorGoalDestroyItems";
this.tag = tag;
this.destroyPercent = destroyPercent;
this.matchTag = matchTag;
this.matchIdentifier = matchIdentifier;
this.matchInventory = matchInventory;
}
}
}
}
@@ -1,162 +0,0 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalEntityTransformation : Goal
{
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[catalystitem]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { catalystItemName });
private bool isCompleted;
public override bool IsCompleted => isCompleted;
private string catalystItemIdentifier, catalystItemName;
private Vector2 activeEntitySavedPosition;
private Entity activeEntity;
private int activeEntityIndex;
private const float gracePeriod = 1f;
private const float graceDistance = 200f;
private float graceTimer;
private double transformationTime;
private enum EntityTypes { Character, Item }
private Identifier[] entities;
private EntityTypes[] entityTypes;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = HasTransformed(deltaTime);
}
public override bool CanBeCompleted(ICollection<Traitor> traitors)
{
return graceTimer <= gracePeriod;
}
private bool HasTransformed(float deltaTime)
{
if (activeEntity != null && !activeEntity.Removed)
{
activeEntitySavedPosition = activeEntity.WorldPosition;
}
else
{
if (transformationTime == 0)
{
graceTimer = 0.0f;
activeEntityIndex++;
transformationTime = Timing.TotalTime;
}
graceTimer += deltaTime;
switch (entityTypes[activeEntityIndex])
{
case EntityTypes.Character:
foreach (Character character in Character.CharacterList)
{
if (character.Submarine == null || Traitors.All(t => character.Submarine.TeamID != t.Character.TeamID) || character.SpawnTime + gracePeriod < transformationTime)
{
continue;
}
if (character.SpeciesName == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, character.WorldPosition) < graceDistance)
{
activeEntity = character;
transformationTime = 0.0;
return activeEntityIndex == entities.Length - 1;
}
}
break;
case EntityTypes.Item:
foreach (Item item in Item.ItemList)
{
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID) || item.SpawnTime + gracePeriod < transformationTime)
{
continue;
}
if (((MapEntity)item).Prefab.Identifier == entities[activeEntityIndex] && Vector2.Distance(activeEntitySavedPosition, item.WorldPosition) < graceDistance)
{
activeEntity = item;
transformationTime = 0.0;
return activeEntityIndex == entities.Length - 1;
}
}
break;
}
}
return false;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
catalystItemName = TextManager.FormatServerMessage($"entityname.{catalystItemIdentifier}");
activeEntity = null;
activeEntityIndex = 0;
switch (entityTypes[activeEntityIndex])
{
case EntityTypes.Character:
foreach (Character character in Character.CharacterList)
{
if (character.Submarine == null || Traitors.All(t => character.Submarine.TeamID != t.Character.TeamID))
{
continue;
}
if (character.SpeciesName == entities[activeEntityIndex])
{
activeEntity = character;
break;
}
}
break;
case EntityTypes.Item:
foreach (Item item in Item.ItemList)
{
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID))
{
continue;
}
if (((MapEntity)item).Prefab.Identifier == entities[0])
{
activeEntity = item;
break;
}
}
break;
}
graceTimer = 0.0f;
return activeEntity != null;
}
public GoalEntityTransformation(string[] entities, string[] entityTypes, string catalystItemIdentifier) : base()
{
this.entities = entities.ToIdentifiers().ToArray();
this.entityTypes = new EntityTypes[entityTypes.Length];
for (int i = 0; i < this.entityTypes.Length; i++)
{
this.entityTypes[i] = (EntityTypes)Enum.Parse(typeof(EntityTypes), entityTypes[i], true);
}
this.catalystItemIdentifier = catalystItemIdentifier;
}
}
}
}
@@ -1,239 +0,0 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class GoalFindItem : HumanoidGoal
{
private readonly TraitorMission.CharacterFilter filter;
private readonly string identifier;
private readonly bool preferNew;
private readonly bool allowNew;
private readonly bool allowExisting;
private readonly HashSet<Identifier> allowedContainerIdentifiers = new HashSet<Identifier>();
private ItemPrefab targetPrefab;
private ItemPrefab containedPrefab;
private Item targetContainer;
private Item target;
private HashSet<Item> existingItems = new HashSet<Item>();
private string targetNameText;
private string targetContainerNameText;
private string targetHullNameText;
private float percentage;
private int spawnAmount = 1;
private const string itemContainerId = "toolbox";
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[identifier]", "[target]", "[targethullname]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetNameText ?? "", targetContainerNameText ?? "", targetHullNameText ?? "" });
public override bool IsCompleted => target != null && Traitors.Any(traitor => traitor.Character.HasItem(target));
public override bool CanBeCompleted(ICollection<Traitor> traitors)
{
if (!base.CanBeCompleted(traitors))
{
return false;
}
if (target == null)
{
var targetPrefabCandidate = FindItemPrefab(identifier);
return targetPrefabCandidate != null && FindTargetContainer(traitors, targetPrefabCandidate) != null;
}
if (target.Removed)
{
return false;
}
if (target.Submarine == null)
{
if (!(target.ParentInventory?.Owner is Character))
{
return false;
}
}
else
{
if (Traitors.All(traitor => target.Submarine.TeamID != traitor.Character.TeamID))
{
return false;
}
}
return true;
}
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (target != null && target.FindParentInventory(inventory => inventory == character.Inventory) != null);
protected ItemPrefab FindItemPrefab(string identifier)
{
return (ItemPrefab)MapEntityPrefab.List.FirstOrDefault(prefab => prefab is ItemPrefab && prefab.Identifier == identifier);
}
protected Item FindRandomContainer(ICollection<Traitor> traitors, ItemPrefab targetPrefabCandidate, bool includeNew, bool includeExisting)
{
List<Item> suitableItems = new List<Item>();
foreach (Item item in Item.ItemList)
{
if (item.HiddenInGame || item.NonInteractable || item.NonPlayerTeamInteractable) { continue; }
if (item.Submarine == null || traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID))
{
continue;
}
if (item.GetComponent<ItemContainer>() != null && allowedContainerIdentifiers.Contains(((MapEntity)item).Prefab.Identifier))
{
if ((includeNew && !item.OwnInventory.IsFull()) || (includeExisting && item.OwnInventory.FindItemByIdentifier(targetPrefabCandidate.Identifier) != null))
{
suitableItems.Add(item);
}
}
}
if (suitableItems.Count == 0) { return null; }
return suitableItems[TraitorManager.RandomInt(suitableItems.Count)];
}
protected Item FindTargetContainer(ICollection<Traitor> traitors, ItemPrefab targetPrefabCandidate)
{
Item result = null;
if (preferNew)
{
result = FindRandomContainer(traitors, targetPrefabCandidate, true, false);
}
if (result == null)
{
result = FindRandomContainer(traitors, targetPrefabCandidate, allowNew, allowExisting);
}
if (result == null)
{
return null;
}
if (allowNew && !result.OwnInventory.IsFull())
{
return result;
}
if (allowExisting && result.OwnInventory.FindItemByIdentifier(targetPrefabCandidate.Identifier) != null)
{
return result;
}
return null;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
if (targetPrefab != null)
{
return true;
}
string targetPrefabTextId;
if (percentage > 0f)
{
spawnAmount = (int)Math.Floor(Character.CharacterList.FindAll(c => c.TeamID == traitor.Character.TeamID && c != traitor.Character && !c.IsDead && (filter == null || filter(c))).Count * percentage);
}
if (spawnAmount > 1 && allowNew)
{
containedPrefab = FindItemPrefab(identifier);
targetPrefab = FindItemPrefab(itemContainerId);
if (containedPrefab == null || targetPrefab == null)
{
return false;
}
targetPrefabTextId = containedPrefab.GetItemNameTextId();
}
else
{
spawnAmount = 1;
containedPrefab = null;
targetPrefab = FindItemPrefab(identifier);
if (targetPrefab == null)
{
return false;
}
targetPrefabTextId = targetPrefab.GetItemNameTextId();
}
targetNameText = targetPrefabTextId != null ? TextManager.FormatServerMessage(targetPrefabTextId) : targetPrefab.Name.Value;
targetContainer = FindTargetContainer(Traitors, targetPrefab);
if (targetContainer == null)
{
targetPrefab = null;
targetContainer = null;
return false;
}
var containerPrefabTextId = targetContainer.Prefab.GetItemNameTextId();
targetContainerNameText = containerPrefabTextId != null ? TextManager.FormatServerMessage(containerPrefabTextId) : targetContainer.Prefab.Name.Value;
var targetHullTextId = targetContainer.CurrentHull?.Prefab.GetHullNameTextId();
targetHullNameText = targetHullTextId != null ? TextManager.FormatServerMessage(targetHullTextId) : targetContainer?.CurrentHull?.DisplayName.Value ?? "";
if (allowNew && !targetContainer.OwnInventory.IsFull())
{
existingItems.Clear();
foreach (var item in targetContainer.OwnInventory.AllItems.Distinct())
{
existingItems.Add(item);
}
Entity.Spawner.AddItemToSpawnQueue(targetPrefab, targetContainer.OwnInventory, onSpawned: item =>
{
item.AddTag("traitormissionitem");
});
target = null;
}
else if (allowExisting)
{
target = targetContainer.OwnInventory.FindItemByIdentifier(targetPrefab.Identifier);
}
else
{
targetPrefab = null;
targetContainer = null;
return false;
}
return true;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (target == null)
{
target = targetContainer.ContainedItems.FirstOrDefault(item => item.Prefab.Identifier == (containedPrefab != null ? itemContainerId : identifier) && !existingItems.Contains(item));
if (target != null)
{
if (containedPrefab != null)
{
for (int i = 0; i < spawnAmount; i++)
{
Entity.Spawner.AddItemToSpawnQueue(containedPrefab, target.OwnInventory);
}
}
existingItems.Clear();
}
}
}
public GoalFindItem(TraitorMission.CharacterFilter filter, string identifier, bool preferNew, bool allowNew, bool allowExisting, float percentage, params Identifier[] allowedContainerIdentifiers)
{
this.filter = filter;
this.identifier = identifier;
this.preferNew = preferNew;
this.allowNew = allowNew;
this.allowExisting = allowExisting;
this.percentage = percentage / 100f;
this.allowedContainerIdentifiers.UnionWith(allowedContainerIdentifiers);
}
}
}
}
@@ -1,46 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalFloodPercentOfSub : Goal
{
private readonly float minimumFloodingAmount;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { string.Format("{0:0}", minimumFloodingAmount * 100.0f) });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
var validHullsCount = 0;
var floodingAmount = 0.0f;
foreach (Hull hull in Hull.HullList)
{
if (hull.Submarine == null || hull.Submarine.Info.IsOutpost || Traitors.All(traitor => hull.Submarine.TeamID != traitor.Character.TeamID)) { continue; }
if (hull.Submarine == GameMain.Server?.RespawnManager?.RespawnShuttle) { continue; }
++validHullsCount;
floodingAmount += hull.WaterVolume / hull.Volume;
}
if (validHullsCount > 0)
{
floodingAmount /= validHullsCount;
}
isCompleted = floodingAmount >= minimumFloodingAmount;
}
public GoalFloodPercentOfSub(float minimumFloodingAmount) : base()
{
InfoTextId = "TraitorGoalFloodPercentOfSub";
this.minimumFloodingAmount = minimumFloodingAmount;
}
}
}
}
@@ -1,84 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalInjectTarget : Goal
{
public TraitorMission.CharacterFilter Filter { get; private set; }
public List<Character> Targets { get; private set; }
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[poison]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { traitor.Mission.GetTargetNames(Targets) ?? "(unknown)", poisonName });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && Targets.Contains(character));
private string poisonId;
private string afflictionId;
private string poisonName;
private int targetCount;
private float targetPercentage;
private bool[] targetWasInfected;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = WereAllTargetsInfected();
}
private bool WereAllTargetsInfected()
{
if (targetWasInfected == null) { return false; }
for (int i = 0; i < targetWasInfected.Length; i++)
{
if (targetWasInfected[i]) continue;
targetWasInfected[i] = Targets[i].CharacterHealth.GetAffliction(afflictionId) != null;
}
return targetWasInfected.All(t => t == true);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
poisonName = TextManager.FormatServerMessage(poisonId) ?? poisonId;
Targets = traitor.Mission.FindKillTarget(traitor.Character, Filter, targetCount, targetPercentage);
if (Targets == null)
{
return false;
}
targetWasInfected = new bool[Targets.Count];
return !Targets.All(t => t.IsDead);
}
public GoalInjectTarget(TraitorMission.CharacterFilter filter, string poisonId, string afflictionId, int targetCount, float targetPercentage) : base()
{
Filter = filter;
this.poisonId = poisonId;
this.afflictionId = afflictionId;
this.targetCount = targetCount;
this.targetPercentage = targetPercentage / 100f;
if (this.targetPercentage < 1.0f)
{
InfoTextId = "traitorgoalpoisoninfo";
}
else
{
InfoTextId = "traitorgoalpoisoneveryoneinfo";
}
}
}
}
}
@@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalKeepTransformedAlive : Goal
{
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[speciesname]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetCharacterName });
public override bool IsCompleted => isCompleted;
private bool isCompleted;
private const float gracePeriod = 1f;
private Identifier speciesId;
private string targetCharacterName;
private Character targetCharacter;
private float timer;
public override bool CanBeCompleted(ICollection<Traitor> traitors)
{
return timer < gracePeriod || targetCharacter != null && !targetCharacter.IsDead;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (timer <= gracePeriod)
{
timer += deltaTime;
}
isCompleted = targetCharacter != null && !targetCharacter.IsDead && timer >= gracePeriod;
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
var startTime = Timing.TotalTime;
foreach (Character character in Character.CharacterList)
{
if (character.Submarine == null || Traitors.All(t => character.Submarine.TeamID != t.Character.TeamID) || character.SpawnTime + gracePeriod < startTime)
{
continue;
}
if (character.SpeciesName == speciesId)
{
targetCharacter = character;
break;
}
}
targetCharacterName = TextManager.FormatServerMessage($"character.{speciesId}").ToLowerInvariant();
return targetCharacter != null;
}
public GoalKeepTransformedAlive(Identifier speciesId) : base()
{
this.speciesId = speciesId;
}
}
}
}
@@ -1,163 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalKillTarget : Goal
{
public TraitorMission.CharacterFilter Filter { get; private set; }
public List<Character> Targets { get; private set; }
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[causeofdeath]", "[targethullname]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[]
{ traitor.Mission.GetTargetNames(Targets) ?? "(unknown)", GetCauseOfDeath().Value, targetHull != null ? TextManager.Get($"roomname.{targetHull}").Value : string.Empty });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && Targets.Contains(character));
private CauseOfDeathType requiredCauseOfDeath;
private string afflictionId;
private string targetHull;
private int targetCount;
private float targetPercentage;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = DoesDeathMatchCriteria();
}
private bool DoesDeathMatchCriteria()
{
if (Targets == null || Targets.Any(t => !t.IsDead)) return false;
bool typeMatch = false;
for (int i = 0; i < Targets.Count; i++)
{
// No specified cause of death required or missing cause of death
if (requiredCauseOfDeath == CauseOfDeathType.Unknown || Targets[i].CauseOfDeath == null)
{
typeMatch = true;
}
else
{
switch (Targets[i].CauseOfDeath.Type)
{
// If a cause of death is labeled as unknown, side with the traitor and accept this regardless of the required type
case CauseOfDeathType.Unknown:
typeMatch = true;
break;
case CauseOfDeathType.Pressure:
case CauseOfDeathType.Suffocation:
case CauseOfDeathType.Drowning:
typeMatch = requiredCauseOfDeath == Targets[i].CauseOfDeath.Type;
break;
case CauseOfDeathType.Affliction:
typeMatch = Targets[i].CauseOfDeath.Type == requiredCauseOfDeath && Targets[i].CauseOfDeath.Affliction.Identifier == afflictionId;
break;
case CauseOfDeathType.Disconnected:
typeMatch = false;
break;
}
}
if (targetHull != null)
{
if (Targets[i].CurrentHull != null)
{
if (typeMatch && Targets[i].CurrentHull.RoomName == targetHull || Targets[i].CurrentHull.RoomName.Contains(targetHull))
{
continue;
}
else
{
return false;
}
}
else
{
// Outside the submarine, not supported for now
return false;
}
}
else
{
if (typeMatch)
{
continue;
}
else
{
return false;
}
}
}
return true;
}
private LocalizedString GetCauseOfDeath()
{
if (requiredCauseOfDeath != CauseOfDeathType.Affliction || afflictionId == string.Empty)
{
return requiredCauseOfDeath.ToString().ToLower();
}
else
{
return TextManager.Get($"afflictionname.{afflictionId}").ToLower();
}
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
Targets = traitor.Mission.FindKillTarget(traitor.Character, Filter, targetCount, targetPercentage);
return Targets != null && !Targets.All(t => t.IsDead);
}
public GoalKillTarget(TraitorMission.CharacterFilter filter, CauseOfDeathType requiredCauseOfDeath, string afflictionId, string targetHull, int targetCount, float targetPercentage) : base()
{
Filter = filter;
this.requiredCauseOfDeath = requiredCauseOfDeath;
this.afflictionId = afflictionId;
this.targetHull = targetHull;
this.targetCount = targetCount;
this.targetPercentage = targetPercentage / 100f;
if (this.targetPercentage < 1f)
{
if (this.requiredCauseOfDeath == CauseOfDeathType.Unknown && targetHull == null)
{
InfoTextId = "traitorgoalkilltargetinfo";
}
else if (this.requiredCauseOfDeath != CauseOfDeathType.Unknown && targetHull == null)
{
InfoTextId = "traitorgoalkilltargetinfowithcause";
}
else if (this.requiredCauseOfDeath == CauseOfDeathType.Unknown && targetHull != null)
{
InfoTextId = "traitorgoalkilltargetinfowithhull";
}
else if (this.requiredCauseOfDeath != CauseOfDeathType.Unknown && targetHull != null)
{
InfoTextId = "traitorgoalkilltargetinfowithcauseandhull";
}
}
else
{
InfoTextId = "traitorgoalkilleveryoneinfo";
}
}
}
}
}
@@ -1,56 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FarseerPhysics;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalReachDistanceFromSub : Goal
{
private readonly float requiredDistance;
private readonly float requiredDistanceSqr;
private float requiredDistanceInMeters;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[distance]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { $"{requiredDistanceInMeters:0.00}" });
public override bool IsCompleted
{
get
{
return Traitors.Any(traitor =>
{
Submarine ownSub = null;
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
if (Submarine.MainSubs[i] != null && Submarine.MainSubs[i].TeamID == traitor.Character.TeamID)
{
ownSub = Submarine.MainSubs[i];
break;
}
}
if (ownSub == null) return false;
var characterPosition = traitor.Character.WorldPosition;
var submarinePosition = ownSub.WorldPosition;
var distance = Vector2.DistanceSquared(characterPosition, submarinePosition);
return distance >= requiredDistanceSqr;
});
}
}
public GoalReachDistanceFromSub(float requiredDistance) : base()
{
InfoTextId = "TraitorGoalReachDistanceFromSub";
requiredDistanceInMeters = requiredDistance;
this.requiredDistance = requiredDistance / Physics.DisplayToRealWorldRatio;
requiredDistanceSqr = this.requiredDistance * this.requiredDistance;
}
}
}
}
@@ -1,70 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class GoalReplaceInventory : HumanoidGoal
{
private readonly HashSet<Identifier> sabotageContainerIds = new HashSet<Identifier>();
private readonly HashSet<Identifier> validReplacementIds = new HashSet<Identifier>();
private readonly float replaceAmount;
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
public override IEnumerable<string> StatusTextKeys => base.StatusTextKeys.Concat(new string[] { "[percentage]" });
public override IEnumerable<string> StatusTextValues(Traitor traitor) => base.StatusTextValues(traitor).Concat(new string[] { string.Format("{0:0}", replaceAmount * 100.0f) });
public override void Update(float deltaTime)
{
base.Update(deltaTime);
int totalAmount = 0, replacedAmount = 0;
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || Traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID))
{
continue;
}
if (item.FindParentInventory(inventory => inventory.Owner is Character) != null)
{
continue;
}
if (sabotageContainerIds.Contains(((MapEntity)item).Prefab.Identifier))
{
++totalAmount;
if (item.OwnInventory.AllItems.All(containedItem => !validReplacementIds.Contains(containedItem.Prefab.Identifier)))
{
continue;
}
++replacedAmount;
}
}
isCompleted = replacedAmount >= (int)(replaceAmount * totalAmount + 0.5f);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
if (sabotageContainerIds.Count <= 0 || validReplacementIds.Count <= 0)
{
return false;
}
return true;
}
public GoalReplaceInventory(Identifier[] containerIds, Identifier[] replacementIds, float replaceAmount)
{
sabotageContainerIds.UnionWith(containerIds);
validReplacementIds.UnionWith(replacementIds);
this.replaceAmount = replaceAmount;
}
}
}
}
@@ -1,62 +0,0 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalSabotageItems : HumanoidGoal
{
private readonly string tag;
private readonly float conditionThreshold;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[tag]", "[target]", "[threshold]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { tag ?? "", targetItemPrefabName ?? "", string.Format("{0:0}", conditionThreshold) });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private readonly List<Item> targetItems = new List<Item>();
private string targetItemPrefabName = null;
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID))
{
continue;
}
if (item.Condition > conditionThreshold && (item.Prefab?.Identifier == tag || item.HasTag(tag)))
{
targetItems.Add(item);
}
}
if (targetItems.Count > 0)
{
var textId = targetItems[0].Prefab.GetItemNameTextId();
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetItems[0].Prefab.Name.Value;
}
return targetItems.Count > 0;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = targetItems.All(item => item.Condition <= conditionThreshold);
}
public GoalSabotageItems(string tag, float conditionThreshold) : base()
{
this.tag = tag;
this.conditionThreshold = conditionThreshold;
InfoTextId = "TraitorGoalSabotageInfo";
}
}
}
}
@@ -1,96 +0,0 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalUnwiring : HumanoidGoal
{
private readonly string tag;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]", "[connectionname]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetItemPrefabName ?? "", targetConnectionDisplayName ?? targetConnectionName });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private readonly List<ConnectionPanel> targetConnectionPanels = new List<ConnectionPanel>();
private string targetItemPrefabName;
private string targetConnectionName;
private string targetConnectionDisplayName;
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID))
{
continue;
}
if (item.Prefab?.Identifier == tag || item.HasTag(tag))
{
var connectionPanel = item.GetComponent<ConnectionPanel>();
if (connectionPanel != null)
{
targetConnectionPanels.Add(connectionPanel);
}
}
}
if (targetConnectionPanels.Count > 0)
{
var textId = targetConnectionPanels[0].Item.Prefab.GetItemNameTextId();
targetItemPrefabName = TextManager.FormatServerMessage(textId) ?? targetConnectionPanels[0].Item.Prefab.Name.Value;
}
return targetConnectionPanels.Count > 0;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
isCompleted = AreTargetsUnwired();
}
private bool AreTargetsUnwired()
{
for (int i = 0; i < targetConnectionPanels.Count; i++)
{
for (int j = 0; j < targetConnectionPanels[i].Connections.Count; j++)
{
if (targetConnectionPanels[i].Connections[j] == null || targetConnectionPanels[i].Connections[j].Wires == null) continue;
if (targetConnectionName != string.Empty)
{
if (targetConnectionPanels[i].Connections[j].Name != targetConnectionName) continue;
}
if (!targetConnectionPanels[i].Connections[j].Wires.All(w => w == null)) return false;
}
}
return true;
}
public GoalUnwiring(string tag, string targetConnectionName, string targetConnectionDisplayTag) : base()
{
this.tag = tag;
this.targetConnectionName = targetConnectionName;
if (targetConnectionDisplayTag != string.Empty)
{
targetConnectionDisplayName = TextManager.FormatServerMessage(targetConnectionDisplayTag);
InfoTextId = "TraitorGoalUnwireInfo";
}
else
{
InfoTextId = "TraitorGoalUnwireAllInfo";
}
}
}
}
}
@@ -1,35 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalWaitForTraitors : Goal
{
private readonly int requiredCount;
private int count = 0;
public override bool IsCompleted => count >= requiredCount;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[remaining]", "[count]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { $"{requiredCount - count}", $"{requiredCount}" });
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
++count;
return true;
}
public GoalWaitForTraitors(int requiredCount) : base()
{
this.requiredCount = requiredCount;
InfoTextId = "TraitorGoalWaitForTraitorsInfoText";
}
}
}
}
@@ -1,17 +0,0 @@
namespace Barotrauma
{
partial class Traitor
{
public abstract class HumanoidGoal : Goal
{
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
return traitor?.Character?.IsHumanoid ?? false;
}
}
}
}
@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalHasDuration : Modifier
{
private readonly float requiredDuration;
private readonly bool countTotalDuration;
private readonly string durationInfoTextId;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[duration]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { requiredDuration.ToString(CultureInfo.InvariantCulture) });
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(durationInfoTextId) && !infoText.Contains("[duration]") ? TextManager.FormatServerMessage(durationInfoTextId,
("[infotext]", infoText), ("[duration]", requiredDuration.ToString(CultureInfo.InvariantCulture))) : infoText;
}
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
private float remainingDuration = float.NaN;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (Goal.IsCompleted)
{
if (!float.IsNaN(remainingDuration))
{
remainingDuration -= deltaTime;
}
else
{
remainingDuration = requiredDuration;
}
isCompleted |= remainingDuration <= 0.0f;
}
else if (!countTotalDuration)
{
remainingDuration = float.NaN;
}
}
public GoalHasDuration(Goal goal, float requiredDuration, bool countTotalDuration, string durationInfoTextId) : base(goal)
{
this.requiredDuration = requiredDuration;
this.countTotalDuration = countTotalDuration;
this.durationInfoTextId = durationInfoTextId;
}
}
}
}
@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalHasTimeLimit : Modifier
{
private readonly float timeLimit;
private readonly string timeLimitInfoTextId;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[timelimit]" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { $"{TimeSpan.FromSeconds(timeLimit):g}" });
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(timeLimitInfoTextId) ? TextManager.FormatServerMessage(timeLimitInfoTextId, ("[infotext]", infoText), ("[timelimit]", $"{TimeSpan.FromSeconds(timeLimit):g}")) : infoText;
}
public override bool CanBeCompleted(ICollection<Traitor> traitors) => base.CanBeCompleted(traitors) && (!Traitors.Any(IsStarted) || timeRemaining > 0.0f);
private float timeRemaining;
public override void Update(float deltaTime)
{
base.Update(deltaTime);
timeRemaining = System.Math.Max(0.0f, timeRemaining - deltaTime);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
timeRemaining = timeLimit;
return true;
}
public GoalHasTimeLimit(Goal goal, float timeLimit, string timeLimitInfoTextId) : base(goal)
{
this.timeLimit = timeLimit;
this.timeLimitInfoTextId = timeLimitInfoTextId;
}
}
}
}
@@ -1,36 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public sealed class GoalIsOptional : Modifier
{
private readonly string optionalInfoTextId;
public override string StatusValueTextId => (Traitors.Any(IsStarted) && !base.CanBeCompleted(Traitors)) ? "failed" : base.StatusValueTextId;
public override IEnumerable<string> StatusTextValues(Traitor traitor)
{
var values = base.StatusTextValues(traitor).ToArray();
values[1] = TextManager.FormatServerMessage(StatusValueTextId);
return values;
}
public override bool IsCompleted => base.IsCompleted || (Traitors.Any(IsStarted) && !base.CanBeCompleted(Traitors));
public override bool CanBeCompleted(ICollection<Traitor> traitors) => true;
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values)
{
var infoText = base.GetInfoText(traitor, textId, keys, values);
return !string.IsNullOrEmpty(optionalInfoTextId) ? TextManager.FormatServerMessage(optionalInfoTextId, ("[infotext]", infoText)) : infoText;
}
public GoalIsOptional(Goal goal, string optionalInfoTextId) : base(goal)
{
this.optionalInfoTextId = optionalInfoTextId;
}
}
}
}
@@ -1,80 +0,0 @@
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public abstract class Modifier : Goal
{
protected Goal Goal { get; }
public override string StatusValueTextId => Goal.StatusValueTextId;
public override string StatusTextId
{
get => Goal.StatusTextId;
set => Goal.StatusTextId = value;
}
public override string InfoTextId
{
get => Goal.InfoTextId;
set => Goal.InfoTextId = value;
}
public override string CompletedTextId
{
get => Goal.CompletedTextId;
set => Goal.CompletedTextId = value;
}
public override IEnumerable<string> StatusTextKeys => Goal.StatusTextKeys;
public override IEnumerable<string> StatusTextValues(Traitor traitor) => new string[] { InfoText(traitor), TextManager.FormatServerMessage(StatusValueTextId) };
public override IEnumerable<string> InfoTextKeys => Goal.InfoTextKeys;
public override IEnumerable<string> InfoTextValues(Traitor traitor) => Goal.InfoTextValues(traitor);
public override IEnumerable<string> CompletedTextKeys => Goal.CompletedTextKeys;
public override IEnumerable<string> CompletedTextValues(Traitor traitor) => Goal.CompletedTextValues(traitor);
protected internal override string GetStatusText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => Goal.GetStatusText(traitor, textId, keys, values);
protected internal override string GetInfoText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => Goal.GetInfoText(traitor, textId, keys, values);
protected internal override string GetCompletedText(Traitor traitor, string textId, IEnumerable<string> keys, IEnumerable<string> values) => Goal.GetCompletedText(traitor, textId, keys, values);
public override string StatusText(Traitor traitor) => GetStatusText(traitor, StatusTextId, StatusTextKeys, StatusTextValues(traitor));
public override string InfoText(Traitor traitor) => GetInfoText(traitor, InfoTextId, InfoTextKeys, InfoTextValues(traitor));
public override string CompletedText(Traitor traitor) => CompletedTextId != null ? GetCompletedText(traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues(traitor)) : StatusText(traitor);
public override bool IsCompleted => Goal.IsCompleted;
public override bool IsStarted(Traitor traitor) => base.IsStarted(traitor) && Goal.IsStarted(traitor);
public override bool CanBeCompleted(ICollection<Traitor> traitors) => base.CanBeCompleted(traitors) && Goal.CanBeCompleted(traitors);
public override bool IsEnemy(Character character) => base.IsEnemy(character) || Goal.IsEnemy(character);
public override void Update(float deltaTime)
{
base.Update(deltaTime);
Goal.Update(deltaTime);
}
public override bool Start(Traitor traitor)
{
if (!base.Start(traitor))
{
return false;
}
if (!Goal.Start(traitor))
{
return false;
}
return true;
}
protected Modifier(Goal goal) : base()
{
Goal = goal;
}
}
}
}
@@ -1,194 +0,0 @@
using Barotrauma.Networking;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Traitor
{
public class Objective
{
public Traitor Traitor { get; private set; }
private int shuffleGoalsCount;
private readonly List<Goal> allGoals = new List<Goal>();
private readonly List<Goal> activeGoals = new List<Goal>();
private readonly List<Goal> pendingGoals = new List<Goal>();
private readonly List<Goal> completedGoals = new List<Goal>();
public bool IsCompleted => pendingGoals.Count <= 0;
public bool IsPartiallyCompleted => completedGoals.Count > 0;
public bool IsStarted { get; private set; } = false;
public bool CanBeStarted(ICollection<Traitor> traitors) => !IsStarted && allGoals.Any(goal => goal.CanBeCompleted(traitors));
public bool CanBeCompleted => !IsStarted || pendingGoals.All(goal => goal.CanBeCompleted(goal.Traitors));
public bool IsEnemy(Character character) => pendingGoals.Any(goal => goal.IsEnemy(character));
public bool IsAllowedToDamage(Structure structure) => pendingGoals.Any(goal => goal.IsAllowedToDamage(structure));
public readonly HashSet<string> Roles = new HashSet<string>();
public string InfoText { get; private set; }
public virtual string GoalInfoFormatId { get; set; } = "TraitorObjectiveGoalInfoFormat";
public string GoalInfos =>
string.Join("/",
string.Join("/", activeGoals.Select((goal, index) =>
{
var statusText = goal.StatusText(Traitor);
var startIndex = statusText.LastIndexOf('/') + 1;
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, ("[statustext]", $"[{index}.st]"))}";
}).ToArray()),
string.Join("", activeGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
public string AllGoalInfos =>
string.Join("/",
string.Join("/", allGoals.Select((goal, index) =>
{
var statusText = goal.StatusText(Traitor);
var startIndex = statusText.LastIndexOf('/') + 1;
return $"{statusText.Substring(0, startIndex)}[{index}.st]={statusText.Substring(startIndex)}/[{index}.sl]={TextManager.FormatServerMessage(GoalInfoFormatId, ("[statustext]", $"[{index}.st]"))}";
}).ToArray()),
string.Join("", allGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
public virtual string StartMessageTextId { get; set; } = "TraitorObjectiveStartMessage";
public virtual IEnumerable<string> StartMessageKeys => new string[] { "[traitorgoalinfos]" };
public virtual IEnumerable<string> StartMessageValues => new string[] { GoalInfos };
public virtual LocalizedString StartMessageText
=> TextManager.FormatServerMessageWithPronouns(Traitor.Character.Info, StartMessageTextId, StartMessageKeys.Zip(StartMessageValues, (k,v) => (k,v)).ToArray());
public virtual string StartMessageServerTextId { get; set; } = "TraitorObjectiveStartMessageServer";
public virtual IEnumerable<string> StartMessageServerKeys => StartMessageKeys.Concat(new string[] { "[traitorname]" });
public virtual IEnumerable<string> StartMessageServerValues => StartMessageValues.Concat(new string[] { Traitor?.Character?.Name ?? "(unknown)" });
public virtual LocalizedString StartMessageServerText => TextManager.FormatServerMessageWithPronouns(Traitor.Character.Info, StartMessageServerTextId, StartMessageServerKeys.Zip(StartMessageServerValues, (k,v) => (k,v)).ToArray());
public virtual string EndMessageSuccessTextId { get; set; } = "TraitorObjectiveEndMessageSuccess";
public virtual string EndMessageSuccessDeadTextId { get; set; } = "TraitorObjectiveEndMessageSuccessDead";
public virtual string EndMessageSuccessDetainedTextId { get; set; } = "TraitorObjectiveEndMessageSuccessDetained";
public virtual string EndMessageFailureTextId { get; set; } = "TraitorObjectiveEndMessageFailure";
public virtual string EndMessageFailureDeadTextId { get; set; } = "TraitorObjectiveEndMessageFailureDead";
public virtual string EndMessageFailureDetainedTextId { get; set; } = "TraitorObjectiveEndMessageFailureDetained";
public virtual IEnumerable<string> EndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
public virtual IEnumerable<string> EndMessageValues => new string[] { Traitor?.Character?.Name ?? "(unknown)", GoalInfos };
public virtual string EndMessageText
{
get
{
var traitorIsDead = Traitor.Character.IsDead;
var traitorIsDetained = Traitor.Character.LockHands;
var messageId = IsCompleted
? (traitorIsDead ? EndMessageSuccessDeadTextId : traitorIsDetained ? EndMessageSuccessDetainedTextId : EndMessageSuccessTextId)
: (traitorIsDead ? EndMessageFailureDeadTextId : traitorIsDetained ? EndMessageFailureDetainedTextId : EndMessageFailureTextId);
return TextManager.FormatServerMessageWithPronouns(Traitor.Character.Info, messageId, EndMessageKeys.Zip(EndMessageValues, (k,v)=>(k,v)).ToArray());
}
}
public bool Start(Traitor traitor)
{
Traitor = traitor;
activeGoals.Clear();
pendingGoals.Clear();
completedGoals.Clear();
var allGoalsCount = allGoals.Count;
var indices = allGoals.Select((goal, index) => index).ToArray();
if (shuffleGoalsCount > 0)
{
for (var i = allGoalsCount; i > 1;)
{
int j = TraitorManager.RandomInt(i--);
var temp = indices[j];
indices[j] = indices[i];
indices[i] = temp;
}
}
for (var i = 0; i < allGoalsCount; ++i)
{
var goal = allGoals[indices[i]];
if (goal.Start(traitor))
{
activeGoals.Add(goal);
pendingGoals.Add(goal);
if (shuffleGoalsCount > 0 && pendingGoals.Count >= shuffleGoalsCount)
{
break;
}
}
else
{
completedGoals.Add(goal);
}
}
if (pendingGoals.Count <= 0 && completedGoals.Count < allGoals.Count)
{
return false;
}
IsStarted = true;
traitor.SendChatMessageBox(StartMessageText.Value, traitor.Mission.Identifier);
traitor.UpdateCurrentObjective(GoalInfos, traitor.Mission.Identifier);
return true;
}
public void StartMessage()
{
Traitor.SendChatMessage(StartMessageText.Value, Traitor.Mission.Identifier);
}
public void EndMessage()
{
Traitor.SendChatMessageBox(EndMessageText, Traitor.Mission.Identifier);
Traitor.SendChatMessage(EndMessageText, Traitor.Mission.Identifier);
}
public void Update(float deltaTime)
{
if (!IsStarted)
{
return;
}
for (int i = 0; i < pendingGoals.Count;)
{
var goal = pendingGoals[i];
goal.Update(deltaTime);
if (!goal.IsCompleted)
{
++i;
}
else
{
completedGoals.Add(goal);
pendingGoals.RemoveAt(i);
if (GameMain.Server != null)
{
Traitor.SendChatMessage(goal.CompletedText(Traitor), Traitor.Mission.Identifier);
if (pendingGoals.Count > 0)
{
Traitor.SendChatMessageBox(goal.CompletedText(Traitor), Traitor.Mission.Identifier);
}
Traitor.UpdateCurrentObjective(GoalInfos, Traitor.Mission.Identifier);
}
}
}
}
public Objective(string infoText, int shuffleGoalsCount, ICollection<string> roles, ICollection<Goal> goals)
{
InfoText = infoText;
this.shuffleGoalsCount = shuffleGoalsCount;
Roles.UnionWith(roles);
allGoals.AddRange(goals);
}
}
}
}
@@ -1,43 +0,0 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class Traitor
{
public readonly Character Character;
public string Role { get; }
public TraitorMission Mission { get; }
public Objective CurrentObjective => Mission.GetCurrentObjective(this);
public Traitor(TraitorMission mission, string role, Character character)
{
Mission = mission;
Role = role;
Character = character;
Character.IsTraitor = true;
GameMain.NetworkMember.CreateEntityEvent(Character, new Character.CharacterStatusEventData());
}
public delegate void MessageSender(string message);
public void SendChatMessage(string serverText, Identifier iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, iconIdentifier, TraitorMessageType.Server);
}
public void SendChatMessageBox(string serverText, Identifier iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, iconIdentifier, TraitorMessageType.ServerMessageBox);
}
public void UpdateCurrentObjective(string objectiveText, Identifier iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
Character.TraitorCurrentObjective = objectiveText;
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective.Value, iconIdentifier, TraitorMessageType.Objective);
}
}
}
@@ -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);
}
}
}
}
@@ -1,394 +0,0 @@
//#define ALLOW_SOLO_TRAITOR
//#define ALLOW_NONHUMANOID_TRAITOR
using System;
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq;
using System.Security.Cryptography;
using Barotrauma.Extensions;
namespace Barotrauma
{
partial class Traitor
{
public class TraitorMission
{
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>();
/// <summary>
/// Has the mission been completed (does not mean that the traitor necessarily won, the mission is considered completed if the traitor fails for whatever reason)
/// </summary>
public 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 Identifier 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.FormatServerMessageWithPronouns(traitor.Character.Info, messageId, GlobalEndMessageKeys.Zip(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, CharacterTeamType 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 != CharacterTeamType.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, CharacterTeamType 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 = ToolBox.SelectWeightedRandom(availableCandidates, availableCandidates.Select(c => Math.Max(c.Item1.RoundsSincePlayedAsTraitor, 0.1f)).ToList(), TraitorManager.Random);
assignedCandidates.Add(Tuple.Create(currentRole, selected));
foreach (var candidate in roleCandidates.Values)
{
candidate.Remove(selected);
}
}
if (unassignedRoles.Count > 0)
{
return null;
}
return assignedCandidates;
}
public bool CanBeStarted(GameServer server, TraitorManager traitorManager, CharacterTeamType 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 bool Start(GameServer server, TraitorManager traitorManager, CharacterTeamType team)
{
var assignedCandidates = AssignTraitors(server, traitorManager, team);
if (assignedCandidates == null)
{
return false;
}
foreach (Client client in server.ConnectedClients)
{
client.RoundsSincePlayedAsTraitor++;
}
Traitors.Clear();
foreach (var candidate in assignedCandidates)
{
var traitor = new Traitor(this, candidate.Item1, candidate.Item2.Item1.Character);
Traitors.Add(candidate.Item1, traitor);
candidate.Item2.Item1.RoundsSincePlayedAsTraitor = 0;
}
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>());
}
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($"{GameServer.CharacterLogName(traitor.Character)} 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 void Update(float deltaTime, TraitorWinHandler winHandler)
{
if (pendingObjectives.Count <= 0 || Traitors.Count <= 0)
{
return;
}
if (Traitors.Values.Any(traitor => traitor.Character == null || traitor.Character.IsDead || traitor.Character.Removed))
{
Traitors.Values.ForEach(traitor => traitor.UpdateCurrentObjective("", Identifier));
pendingObjectives.Clear();
Traitors.Clear();
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);
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 List<Character> FindKillTarget(Character traitor, CharacterFilter filter, int count = -1, float percentage = -1f)
{
if (traitor == null) { return null; }
List<Character> validCharacters = Character.CharacterList.FindAll(c => c.TeamID == traitor.TeamID &&
c != traitor && !c.IsDead &&
(filter == null || filter(c)));
int targetCount = 1;
if (count > 0)
{
targetCount = count;
}
else if (percentage > 0f)
{
targetCount = (int)Math.Max(1, Math.Floor(validCharacters.Count * percentage));
}
List<Character> targetCharacters = new List<Character>();
if (validCharacters.Count > 0)
{
for (int i = 0; i < targetCount; i++)
{
if (validCharacters.Count == 0) break;
Character character = validCharacters[TraitorManager.RandomInt(validCharacters.Count)];
targetCharacters.Add(character);
validCharacters.Remove(character);
}
return targetCharacters;
}
#if ALLOW_SOLO_TRAITOR
targetCharacters.Add(traitor);
return targetCharacters;
#else
return null;
#endif
}
public string GetTargetNames(List<Character> targets)
{
string names = string.Empty;
for (int i = 0; i < targets.Count; i++)
{
names += targets[i].Name;
if (i < targets.Count - 1)
{
names += ", ";
}
}
if (names.Length > 0)
{
return names;
}
else
{
return TextManager.FormatServerMessage("unknown");
}
}
public TraitorMission(Identifier 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);
}
}
}
}
@@ -1,664 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
class TraitorMissionPrefab
{
public class TraitorMissionEntry : Prefab
{
public static PrefabCollection<TraitorMissionEntry> Prefabs => TraitorMissionPrefab.Prefabs;
public readonly TraitorMissionPrefab Prefab;
public float SelectedWeight;
public TraitorMissionEntry(ContentXElement element, TraitorMissionsFile file) : base(file, element)
{
Prefab = new TraitorMissionPrefab(element);
}
public override void Dispose() { }
}
public static readonly PrefabCollection<TraitorMissionEntry> Prefabs = new PrefabCollection<TraitorMissionEntry>();
public static TraitorMissionPrefab RandomPrefab()
{
var selected = ToolBox.SelectWeightedRandom(Prefabs.ToList(), Prefabs.Select(mission => Math.Max(mission.SelectedWeight, 0.1f)).ToList(), TraitorManager.Random);
//the weight of the missions that didn't get selected keeps growing the make them more likely to get picked
foreach (var mission in Prefabs)
{
mission.SelectedWeight += 10;
}
selected.SelectedWeight = 0.0f;
return selected.Prefab;
}
private class AttributeChecker : IDisposable
{
private readonly XElement element;
private readonly HashSet<string> required = new HashSet<string>();
private readonly HashSet<string> optional = new HashSet<string>();
public void Optional(params string[] names)
{
optional.UnionWith(names);
}
public void Required(params string[] names)
{
required.UnionWith(names);
}
public void Dispose()
{
foreach (var requiredName in required)
{
if (element.Attributes().All(attribute => attribute.Name != requiredName))
{
GameServer.Log($"Required attribute \"{requiredName}\" is missing in \"{element.Name}\"", ServerLog.MessageType.Error);
}
}
foreach (var attribute in element.Attributes())
{
var attributeName = attribute.Name.ToString();
if (!required.Contains(attributeName) && !optional.Contains(attributeName))
{
GameServer.Log($"Unsupported attribute \"{attributeName}\" in \"{element.Name}\"", ServerLog.MessageType.Error);
}
}
}
public AttributeChecker(XElement element)
{
this.element = element;
}
}
public class Goal
{
public readonly string Type;
public readonly XElement Config;
public Goal(string type, XElement config)
{
Type = type;
Config = config;
}
private delegate bool TargetFilter(string value, Character character);
private static Dictionary<string, TargetFilter> targetFilters = new Dictionary<string, TargetFilter>()
{
{ "job", (value, character) => value == character.Info.Job.Prefab.Identifier },
{ "role", (value, character) => value.Equals(GameMain.Server.TraitorManager.GetTraitorRole(character), StringComparison.OrdinalIgnoreCase) }
};
public Traitor.Goal Instantiate()
{
Traitor.Goal goal = null;
using (var checker = new AttributeChecker(Config))
{
checker.Required("type");
var goalType = Config.GetAttributeString("type", "");
switch (goalType.ToLowerInvariant())
{
case "killtarget":
{
checker.Optional(targetFilters.Keys.ToArray());
checker.Optional("causeofdeath");
checker.Optional("affliction");
checker.Optional("roomname");
checker.Optional("targetcount");
checker.Optional("targetpercentage");
List<Traitor.TraitorMission.CharacterFilter> killFilters = new List<Traitor.TraitorMission.CharacterFilter>();
foreach (var attribute in Config.Attributes())
{
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
{
killFilters.Add((character) => filter(attribute.Value, character));
}
}
goal = new Traitor.GoalKillTarget((character) => killFilters.All(f => f(character)),
(CauseOfDeathType)Enum.Parse(typeof(CauseOfDeathType), Config.GetAttributeString("causeofdeath", "Unknown"), true),
Config.GetAttributeString("affliction", null), Config.GetAttributeString("targethull", null), Config.GetAttributeInt("targetcount", -1),
Config.GetAttributeFloat("targetpercentage", -1f));
break;
}
case "destroyitems":
{
checker.Required("tag");
checker.Optional("percentage", "matchIdentifier", "matchTag", "matchInventory");
var tag = Config.GetAttributeString("tag", null);
if (tag != null)
{
goal = new Traitor.GoalDestroyItemsWithTag(
tag,
Config.GetAttributeFloat("percentage", 100.0f) / 100.0f,
Config.GetAttributeBool("matchIdentifier", true),
Config.GetAttributeBool("matchTag", true),
Config.GetAttributeBool("matchInventory", false));
}
break;
}
case "sabotage":
{
checker.Required("tag");
checker.Optional("threshold");
var tag = Config.GetAttributeString("tag", null);
if (tag != null)
{
goal = new Traitor.GoalSabotageItems(tag, Config.GetAttributeFloat("threshold", 20.0f));
}
break;
}
case "floodsub":
checker.Optional("percentage");
goal = new Traitor.GoalFloodPercentOfSub(Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
break;
case "finditem":
checker.Required("identifier");
checker.Optional("preferNew", "allowNew", "allowExisting", "allowedContainers", "percentage");
List<Traitor.TraitorMission.CharacterFilter> itemCountFilters = new List<Traitor.TraitorMission.CharacterFilter>();
foreach (var attribute in Config.Attributes())
{
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
{
itemCountFilters.Add((character) => filter(attribute.Value, character));
}
}
goal = new Traitor.GoalFindItem((character) => itemCountFilters.All(f => f(character)),
Config.GetAttributeString("identifier",
null),
Config.GetAttributeBool("preferNew",
true),
Config.GetAttributeBool("allowNew",
true),
Config.GetAttributeBool("allowExisting",
true),
Config.GetAttributeFloat("percentage",
-1f),
Config.GetAttributeIdentifierArray("allowedContainers",
new string[]
{
"steelcabinet",
"mediumsteelcabinet",
"suppliescabinet"
}.ToIdentifiers()));
break;
case "replaceinventory":
checker.Required("containers", "replacements");
checker.Optional("percentage");
goal = new Traitor.GoalReplaceInventory(Config.GetAttributeIdentifierArray("containers", new Identifier[] { }), Config.GetAttributeIdentifierArray("replacements", new Identifier[] { }), Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
break;
case "reachdistancefromsub":
checker.Optional("distance");
goal = new Traitor.GoalReachDistanceFromSub(Config.GetAttributeFloat("distance", 125f));
break;
case "injectpoison":
checker.Optional(targetFilters.Keys.ToArray());
checker.Required("poison");
checker.Required("affliction");
checker.Optional("targetcount");
checker.Optional("targetpercentage");
List<Traitor.TraitorMission.CharacterFilter> poisonFilters = new List<Traitor.TraitorMission.CharacterFilter>();
foreach (var attribute in Config.Attributes())
{
if (targetFilters.TryGetValue(attribute.Name.ToString().ToLower(System.Globalization.CultureInfo.InvariantCulture), out var filter))
{
poisonFilters.Add((character) => filter(attribute.Value, character));
}
}
goal = new Traitor.GoalInjectTarget((character) => poisonFilters.All(f => f(character)), Config.GetAttributeString("poison", null),
Config.GetAttributeString("affliction", null), Config.GetAttributeInt("targetcount", -1), Config.GetAttributeFloat("targetpercentage", -1f));
break;
case "unwire":
checker.Required("tag");
checker.Optional("connectionname");
checker.Optional("connectiondisplayname");
goal = new Traitor.GoalUnwiring(Config.GetAttributeString("tag", null), Config.GetAttributeString("connectionname", null), Config.GetAttributeString("connectiondisplayname)", null));
break;
case "transformentity":
checker.Required("entities", "entitytypes");
checker.Optional("catalystid");
goal = new Traitor.GoalEntityTransformation(Config.GetAttributeStringArray("entities", null), Config.GetAttributeStringArray("entitytypes", null), Config.GetAttributeString("catalystid", null));
break;
case "keeptransformedalive":
checker.Required("speciesname");
goal = new Traitor.GoalKeepTransformedAlive(Config.GetAttributeIdentifier("speciesname", Identifier.Empty));
break;
default:
GameServer.Log($"Unrecognized goal type \"{goalType}\".", ServerLog.MessageType.Error);
break;
}
}
if (goal == null)
{
return null;
}
foreach (var element in Config.Elements())
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "modifier":
{
using (var checker = new AttributeChecker(element))
{
checker.Required("type");
var modifierType = element.GetAttributeString("type", "");
switch (modifierType)
{
case "duration":
{
checker.Optional("cumulative", "duration", "infotext");
var isCumulative = element.GetAttributeBool("cumulative", false);
goal = new Traitor.GoalHasDuration(goal, element.GetAttributeFloat("duration", 5.0f), isCumulative, element.GetAttributeString("infotext", isCumulative ? "TraitorGoalWithCumulativeDurationInfoText" : "TraitorGoalWithDurationInfoText"));
break;
}
case "timelimit":
checker.Optional("timelimit", "infotext");
goal = new Traitor.GoalHasTimeLimit(goal, element.GetAttributeFloat("timelimit", 180.0f), element.GetAttributeString("infotext", "TraitorGoalWithTimeLimitInfoText"));
break;
case "optional":
checker.Optional("infotext");
goal = new Traitor.GoalIsOptional(goal, element.GetAttributeString("infotext", "TraitorGoalIsOptionalInfoText"));
break;
default:
GameServer.Log($"Unrecognized modifier type \"{modifierType}\".", ServerLog.MessageType.Error);
break;
}
}
break;
}
}
}
foreach (var element in Config.Elements())
{
var elementName = element.Name.ToString().ToLowerInvariant();
switch (elementName)
{
case "modifier":
// loaded above
break;
case "infotext":
{
using (var checker = new AttributeChecker(element))
{
checker.Required("id");
var id = element.GetAttributeString("id", null);
if (id != null)
{
goal.InfoTextId = id;
}
}
break;
}
case "completedtext":
{
using (var checker = new AttributeChecker(element))
{
checker.Required("id");
var id = element.GetAttributeString("id", null);
if (id != null)
{
goal.CompletedTextId = id;
}
}
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\" in goal.", ServerLog.MessageType.Error);
break;
}
}
return goal;
}
}
public abstract class ObjectiveBase
{
public HashSet<string> Roles { get; } = new HashSet<string>();
public abstract void InstantiateGoals();
public abstract Traitor.Objective Instantiate(IEnumerable<string> roles);
}
protected class Objective : ObjectiveBase
{
public string InfoText { get; internal set; }
public string StartMessageTextId { get; internal set; }
public string StartMessageServerTextId { get; internal set; }
public string EndMessageSuccessTextId { get; internal set; }
public string EndMessageSuccessDeadTextId { get; internal set; }
public string EndMessageSuccessDetainedTextId { get; internal set; }
public string EndMessageFailureTextId { get; internal set; }
public string EndMessageFailureDeadTextId { get; internal set; }
public string EndMessageFailureDetainedTextId { get; internal set; }
public int ShuffleGoalsCount { get; internal set; }
public readonly List<Goal> Goals = new List<Goal>();
private List<Traitor.Goal> goalInstances = null;
public override void InstantiateGoals()
{
goalInstances = Goals.ConvertAll(goal =>
{
var instance = goal.Instantiate();
if (instance == null)
{
GameServer.Log($"Failed to instantiate goal \"{goal.Type}\".", ServerLog.MessageType.Error);
}
return instance;
}).FindAll(goal => goal != null);
}
public override Traitor.Objective Instantiate(IEnumerable<string> roles)
{
var result = new Traitor.Objective(InfoText, ShuffleGoalsCount, roles.ToArray(), goalInstances);
if (StartMessageTextId != null)
{
result.StartMessageTextId = StartMessageTextId;
}
if (StartMessageServerTextId != null)
{
result.StartMessageServerTextId = StartMessageServerTextId;
}
if (EndMessageSuccessTextId != null)
{
result.EndMessageSuccessTextId = EndMessageSuccessTextId;
}
if (EndMessageSuccessDeadTextId != null)
{
result.EndMessageSuccessDeadTextId = EndMessageSuccessDeadTextId;
}
if (EndMessageSuccessDetainedTextId != null)
{
result.EndMessageSuccessDetainedTextId = EndMessageSuccessDetainedTextId;
}
if (EndMessageFailureTextId != null)
{
result.EndMessageFailureTextId = EndMessageFailureTextId;
}
if (EndMessageFailureDeadTextId != null)
{
result.EndMessageFailureDeadTextId = EndMessageFailureDeadTextId;
}
if (EndMessageFailureDetainedTextId != null)
{
result.EndMessageFailureDetainedTextId = EndMessageFailureDetainedTextId;
}
return result;
}
}
protected class WaitObjective : ObjectiveBase
{
private Traitor.GoalWaitForTraitors sharedGoal;
public override void InstantiateGoals()
{
sharedGoal = new Traitor.GoalWaitForTraitors(Roles.Count);
}
public override Traitor.Objective Instantiate(IEnumerable<string> roles)
{
return new Traitor.Objective("TraitorObjectiveInfoTextWaitForOtherTraitors", -1, roles.ToArray(), new[] { sharedGoal });
}
public WaitObjective(ICollection<string> roles)
{
Roles.UnionWith(roles);
}
}
public class Role
{
public readonly Traitor.TraitorMission.RoleFilter Filter;
public Role(IEnumerable<Traitor.TraitorMission.RoleFilter> filters)
{
Filter = character => filters.All(filter => filter(character));
}
public Role()
{
Filter = character => true;
}
}
public readonly Dictionary<string, Role> Roles = new Dictionary<string, Role>();
public readonly Identifier Identifier;
public readonly string StartText;
public readonly string EndMessageSuccessText;
public readonly string EndMessageSuccessDeadText;
public readonly string EndMessageSuccessDetainedText;
public readonly string EndMessageFailureText;
public readonly string EndMessageFailureDeadText;
public readonly string EndMessageFailureDetainedText;
public readonly List<ObjectiveBase> Objectives = new List<ObjectiveBase>();
public Traitor.TraitorMission Instantiate()
{
var objectivesWithSync = new List<ObjectiveBase>();
var objectivesCount = Objectives.Count;
if (objectivesCount > 0)
{
var pendingRoles = new HashSet<string>();
var pendingCount = 1;
objectivesWithSync.Add(Objectives[0]);
pendingRoles.UnionWith(Objectives[0].Roles);
for (var i = 1; i < objectivesCount; ++i)
{
var objective = Objectives[i];
if (pendingRoles.IsSupersetOf(objective.Roles))
{
if (pendingCount > 1)
{
objectivesWithSync.Add(new WaitObjective(objective.Roles));
}
pendingRoles.Clear();
pendingCount = 0;
}
objectivesWithSync.Add(objective);
pendingRoles.UnionWith(objective.Roles);
++pendingCount;
}
if (pendingCount > 1 && pendingRoles.IsSubsetOf(Roles.Keys))
{
// TODO: If last objective includes only one traitor, other traitors will get the wrong end message.
objectivesWithSync.Add(new WaitObjective(Roles.Keys));
}
}
return new Traitor.TraitorMission(
Identifier,
StartText ?? "TraitorMissionStartMessage",
EndMessageSuccessText ?? "TraitorObjectiveEndMessageSuccess",
EndMessageSuccessDeadText ?? "TraitorObjectiveEndMessageSuccessDead",
EndMessageSuccessDetainedText ?? "TraitorObjectiveEndMessageSuccessDetained",
EndMessageFailureText ?? "TraitorObjectiveEndMessageFailure",
EndMessageFailureDeadText ?? "TraitorObjectiveEndMessageFailureDead",
EndMessageFailureDetainedText ?? "TraitorObjectiveEndMessageFailureDetained",
Roles.ToDictionary(kv => kv.Key, kv => kv.Value.Filter),
objectivesWithSync.SelectMany(objective =>
{
objective.InstantiateGoals();
return objective.Roles.Select(role => objective.Instantiate(new[] { role }));
}).ToArray());
}
protected Goal LoadGoal(XElement goalRoot)
{
var goalType = goalRoot.GetAttributeString("type", "");
return new Goal(goalType, goalRoot);
}
protected Objective LoadObjective(XElement objectiveRoot, string[] allRoles)
{
var allRolesSet = new HashSet<string>(allRoles);
var result = new Objective
{
ShuffleGoalsCount = objectiveRoot.GetAttributeInt("shuffleGoalsCount", -1)
};
var objectiveRoles = objectiveRoot.GetAttributeStringArray("roles", allRoles);
if (!allRolesSet.IsSupersetOf(objectiveRoles))
{
var unrecognized = new HashSet<string>(objectiveRoles);
unrecognized.ExceptWith(allRoles);
GameServer.Log($"Undefined role(s) \"{string.Join(", ", unrecognized)}\" set for Objective.", ServerLog.MessageType.Error);
}
result.Roles.UnionWith(allRolesSet.Intersect(objectiveRoles));
foreach (var element in objectiveRoot.Elements())
{
using (var checker = new AttributeChecker(element))
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "infotext":
checker.Required("id");
result.InfoText = element.GetAttributeString("id", null);
break;
case "startmessage":
checker.Required("id");
result.StartMessageTextId = element.GetAttributeString("id", null);
break;
case "startmessageserver":
checker.Required("id");
result.StartMessageServerTextId = element.GetAttributeString("id", null);
break;
case "endmessagesuccess":
checker.Required("id");
result.EndMessageSuccessTextId = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdead":
checker.Required("id");
result.EndMessageSuccessDeadTextId = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdetained":
checker.Required("id");
result.EndMessageSuccessDetainedTextId = element.GetAttributeString("id", null);
break;
case "endmessagefailure":
checker.Required("id");
result.EndMessageFailureTextId = element.GetAttributeString("id", null);
break;
case "endmessagefailuredead":
checker.Required("id");
result.EndMessageFailureDeadTextId = element.GetAttributeString("id", null);
break;
case "endmessagefailuredetained":
checker.Required("id");
result.EndMessageFailureDetainedTextId = element.GetAttributeString("id", null);
break;
case "goal":
{
var goal = LoadGoal(element);
if (goal != null)
{
result.Goals.Add(goal);
}
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\" under Objective.", ServerLog.MessageType.Error);
break;
}
}
}
return result;
}
protected Role LoadRole(XElement roleRoot)
{
var filters = new List<Traitor.TraitorMission.RoleFilter>();
var jobs = roleRoot.GetAttributeStringArray("jobs", null);
if (jobs != null)
{
var jobsSet = new HashSet<string>(jobs.Select(job => job.ToLower(CultureInfo.InvariantCulture)));
filters.Add(character => character.Info?.Job != null && jobsSet.Contains(character.Info.Job.Name.ToLower().Value));
}
return new Role(filters);
}
public TraitorMissionPrefab(ContentXElement missionRoot)
{
Identifier = missionRoot.GetAttributeIdentifier("identifier", Identifier.Empty);
foreach (var element in missionRoot.Elements())
{
using (var checker = new AttributeChecker(element))
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "role":
checker.Required("id");
checker.Optional("jobs");
Roles.Add(element.GetAttributeString("id", null), LoadRole(element));
break;
}
}
}
if (!Roles.Any())
{
Roles.Add("traitor", new Role());
}
foreach (var element in missionRoot.Elements())
{
using (var checker = new AttributeChecker(element))
{
switch (element.Name.ToString().ToLowerInvariant())
{
case "role":
// handled above
break;
case "startinfotext":
checker.Required("id");
StartText = element.GetAttributeString("id", null);
break;
case "endmessagesuccess":
checker.Required("id");
EndMessageSuccessText = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdead":
checker.Required("id");
EndMessageSuccessDeadText = element.GetAttributeString("id", null);
break;
case "endmessagesuccessdetained":
checker.Required("id");
EndMessageSuccessDetainedText = element.GetAttributeString("id", null);
break;
case "endmessagefailure":
checker.Required("id");
EndMessageFailureText = element.GetAttributeString("id", null);
break;
case "endmessagefailuredead":
checker.Required("id");
EndMessageFailureDeadText = element.GetAttributeString("id", null);
break;
case "endmessagefailuredetained":
checker.Required("id");
EndMessageFailureDetainedText = element.GetAttributeString("id", null);
break;
case "objective":
{
var objective = LoadObjective(element, Roles.Keys.ToArray());
if (objective != null)
{
Objectives.Add(objective);
}
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\"under TraitorMission.", ServerLog.MessageType.Error);
break;
}
}
}
}
}
}
@@ -1,30 +0,0 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class TraitorMissionResult
{
public TraitorMissionResult(Traitor.TraitorMission mission)
{
MissionIdentifier = mission.Identifier;
EndMessage = mission.GlobalEndMessage;
Success = mission.IsCompleted;
foreach (Traitor traitor in mission.Traitors.Values)
{
Characters.Add(traitor.Character);
}
}
public void ServerWrite(IWriteMessage msg)
{
msg.WriteIdentifier(MissionIdentifier);
msg.WriteString(EndMessage);
msg.WriteBoolean(Success);
msg.WriteByte((byte)Characters.Count);
foreach (Character character in Characters)
{
msg.WriteUInt16(character.ID);
}
}
}
}