(d9829ac) v0.9.4.0

This commit is contained in:
Regalis
2019-10-24 18:05:42 +02:00
parent 9aa12bcac2
commit b39922a074
319 changed files with 12516 additions and 6815 deletions
@@ -9,7 +9,7 @@ namespace Barotrauma
{
public abstract class Goal
{
public Traitor Traitor { get; private set; }
public HashSet<Traitor> Traitors { get; } = new HashSet<Traitor>();
public TraitorMission Mission { get; internal set; }
public virtual string StatusTextId { get; set; } = "TraitorGoalStatusTextFormat";
@@ -21,13 +21,13 @@ namespace Barotrauma
public virtual string StatusValueTextId => IsCompleted ? "complete" : "inprogress";
public virtual IEnumerable<string> StatusTextKeys => new [] { "[infotext]", "[status]" };
public virtual IEnumerable<string> StatusTextValues => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
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 => new string[] { };
public virtual IEnumerable<string> InfoTextValues(Traitor traitor) => new string[] { };
public virtual IEnumerable<string> CompletedTextKeys => new string[] { };
public virtual IEnumerable<string> CompletedTextValues => 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.FormatServerMessageWithGenderPronouns(traitor?.Character?.Info?.Gender ?? Gender.None, textId, keys, values);
@@ -35,20 +35,19 @@ namespace Barotrauma
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 => GetStatusText(Traitor, StatusTextId, StatusTextKeys, StatusTextValues);
public virtual string InfoText => GetInfoText(Traitor, InfoTextId, InfoTextKeys, InfoTextValues);
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 => CompletedTextId != null ? GetCompletedText(Traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues) : StatusText;
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 != null;
public virtual bool CanBeCompleted => !(Traitor?.Character?.IsDead ?? true);
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)
{
Traitor = traitor;
Traitors.Add(traitor);
return true;
}
@@ -14,7 +14,7 @@ namespace Barotrauma
private readonly bool matchInventory;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]", "[tag]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { string.Format("{0:0}", DestroyPercent * 100.0f), tagPrefabName ?? "" });
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;
@@ -31,7 +31,7 @@ namespace Barotrauma
int result = 0;
foreach (var item in Item.ItemList)
{
if (!matchInventory && item.FindParentInventory(inventory => inventory.Owner is Character && inventory.Owner != Traitor.Character) != null)
if (!matchInventory && Traitors.All(traitor => item.FindParentInventory(inventory => inventory.Owner is Character && inventory.Owner != traitor.Character) != null))
{
continue;
}
@@ -42,7 +42,7 @@ namespace Barotrauma
}
else
{
if (item.Submarine.TeamID != Traitor.Character.TeamID) { continue; }
if (Traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID)) { continue; }
}
if (item.Condition <= 0.0f)
@@ -24,40 +24,39 @@ namespace Barotrauma
private string targetHullNameText;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[identifier]", "[target]", "[targethullname]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { targetNameText ?? "", targetContainerNameText ?? "", targetHullNameText ?? "" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { targetNameText ?? "", targetContainerNameText ?? "", targetHullNameText ?? "" });
public override bool IsCompleted => target != null && target.ParentInventory == Traitor.Character.Inventory;
public override bool CanBeCompleted {
get
public override bool IsCompleted => target != null && Traitors.Any(traitor => traitor.Character.HasItem(target));
public override bool CanBeCompleted(ICollection<Traitor> traitors)
{
if (!base.CanBeCompleted(traitors))
{
if (!base.CanBeCompleted)
{
return false;
}
if (target == null)
{
return true;
}
if (target.Removed)
{
return false;
}
if (target.Submarine == null)
{
if (!(target.ParentInventory?.Owner is Character))
{
return false;
}
}
else
{
if (target.Submarine.TeamID != Traitor.Character.TeamID)
{
return false;
}
}
return true;
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);
@@ -67,27 +66,51 @@ namespace Barotrauma
return (ItemPrefab)MapEntityPrefab.List.Find(prefab => prefab is ItemPrefab && prefab.Identifier == identifier);
}
protected Item FindRandomContainer(bool includeNew, bool includeExisting)
protected Item FindRandomContainer(ICollection<Traitor> traitors, ItemPrefab targetPrefabCandidate, bool includeNew, bool includeExisting)
{
int itemsCount = Item.ItemList.Count;
int startIndex = TraitorMission.Random(itemsCount);
Item fallback = null;
for (int i = 0; i < itemsCount; ++i)
List<Item> suitableItems = new List<Item>();
foreach (Item item in Item.ItemList)
{
var item = Item.ItemList[(i + startIndex) % itemsCount];
if (item.Submarine == null || item.Submarine.TeamID != Traitor.Character.TeamID)
if (item.Submarine == null || traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID))
{
continue;
}
if (item.GetComponent<ItemContainer>() != null && allowedContainerIdentifiers.Contains(item.prefab.Identifier))
{
if ((includeNew && !item.OwnInventory.IsFull()) || (includeExisting && item.OwnInventory.FindItemByIdentifier(targetPrefab.Identifier) != null))
if ((includeNew && !item.OwnInventory.IsFull()) || (includeExisting && item.OwnInventory.FindItemByIdentifier(targetPrefabCandidate.Identifier) != null))
{
return item;
suitableItems.Add(item);
}
}
}
if (suitableItems.Count == 0) { return null; }
return suitableItems[TraitorMission.Random(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;
}
@@ -97,6 +120,10 @@ namespace Barotrauma
{
return false;
}
if (targetPrefab != null)
{
return true;
}
targetPrefab = FindItemPrefab(identifier);
if (targetPrefab == null)
{
@@ -104,22 +131,16 @@ namespace Barotrauma
}
var targetPrefabTextId = targetPrefab.GetItemNameTextId();
targetNameText = targetPrefabTextId != null ? TextManager.FormatServerMessage(targetPrefabTextId) : targetPrefab.Name;
targetContainer = null;
if (preferNew)
{
targetContainer = FindRandomContainer(true, false);
}
if (targetContainer == null)
{
targetContainer = FindRandomContainer(allowNew, allowExisting);
}
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;
var targetHullTextId = targetContainer.CurrentHull != null ? targetContainer.CurrentHull.prefab.GetHullNameTextId() : null;
var targetHullTextId = targetContainer.CurrentHull?.prefab.GetHullNameTextId();
targetHullNameText = targetHullTextId != null ? TextManager.FormatServerMessage(targetHullTextId) : targetContainer?.CurrentHull?.DisplayName ?? "";
if (allowNew && !targetContainer.OwnInventory.IsFull())
{
@@ -135,6 +156,12 @@ namespace Barotrauma
{
target = targetContainer.OwnInventory.FindItemByIdentifier(targetPrefab.Identifier);
}
else
{
targetPrefab = null;
targetContainer = null;
return false;
}
return true;
}
@@ -11,7 +11,7 @@ namespace Barotrauma
private readonly float minimumFloodingAmount;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[percentage]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { string.Format("{0:0}", minimumFloodingAmount * 100.0f) });
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;
@@ -23,7 +23,7 @@ namespace Barotrauma
var floodingAmount = 0.0f;
foreach (Hull hull in Hull.hullList)
{
if (hull.Submarine == null || hull.Submarine.IsOutpost || hull.Submarine.TeamID != Traitor.Character.TeamID) { continue; }
if (hull.Submarine == null || hull.Submarine.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;
@@ -12,7 +12,7 @@ namespace Barotrauma
public Character Target { get; private set; }
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { Target?.Name ?? "(unknown)" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { Target?.Name ?? "(unknown)" });
private bool isCompleted = false;
public override bool IsCompleted => isCompleted;
@@ -14,20 +14,23 @@ namespace Barotrauma
private readonly float requiredDistanceSqr;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[distance]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{requiredDistance:0.00}" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { $"{requiredDistance:0.00}" });
public override bool IsCompleted
{
get
{
if (Traitor == null || Traitor.Character == null || Traitor.Character.Submarine == null)
return Traitors.Any(traitor =>
{
return false;
}
var characterPosition = Traitor.Character.WorldPosition;
var submarinePosition = Traitor.Character.Submarine.WorldPosition;
var distance = Vector2.DistanceSquared(characterPosition, submarinePosition);
return distance >= requiredDistanceSqr;
if (traitor.Character?.Submarine == null)
{
return false;
}
var characterPosition = traitor.Character.WorldPosition;
var submarinePosition = traitor.Character.Submarine.WorldPosition;
var distance = Vector2.DistanceSquared(characterPosition, submarinePosition);
return distance >= requiredDistanceSqr;
});
}
}
@@ -17,7 +17,7 @@ namespace Barotrauma
public override bool IsCompleted => isCompleted;
public override IEnumerable<string> StatusTextKeys => base.StatusTextKeys.Concat(new string[] { "[percentage]" });
public override IEnumerable<string> StatusTextValues => base.StatusTextValues.Concat(new string[] { string.Format("{0:0}", replaceAmount * 100.0f) });
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)
{
@@ -25,7 +25,7 @@ namespace Barotrauma
int totalAmount = 0, replacedAmount = 0;
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || item.Submarine.TeamID != Traitor.Character.TeamID)
if (item.Submarine == null || Traitors.All(traitor => item.Submarine.TeamID != traitor.Character.TeamID))
{
continue;
}
@@ -12,7 +12,7 @@ namespace Barotrauma
private readonly float conditionThreshold;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[tag]", "[target]", "[threshold]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { tag ?? "", targetItemPrefabName ?? "", string.Format("{0:0}", conditionThreshold) });
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;
@@ -28,7 +28,7 @@ namespace Barotrauma
}
foreach (var item in Item.ItemList)
{
if (item.Submarine == null || item.Submarine.TeamID != Traitor.Character.TeamID)
if (item.Submarine == null || Traitors.All(t => item.Submarine.TeamID != t.Character.TeamID))
{
continue;
}
@@ -0,0 +1,35 @@
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";
}
}
}
}
@@ -10,7 +10,7 @@ namespace Barotrauma
{
return false;
}
return Traitor?.Character?.IsHumanoid ?? false;
return traitor?.Character?.IsHumanoid ?? false;
}
}
}
@@ -14,12 +14,12 @@ namespace Barotrauma
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[duration]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(requiredDuration):g}" });
public override IEnumerable<string> InfoTextValues(Traitor traitor) => base.InfoTextValues(traitor).Concat(new string[] { requiredDuration.ToString() });
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) ? TextManager.FormatServerMessage(durationInfoTextId, new[] { "[infotext]", "[duration]" }, new[] { infoText, $"{TimeSpan.FromSeconds(requiredDuration):g}" }) : infoText;
return !string.IsNullOrEmpty(durationInfoTextId) && !infoText.Contains("[duration]") ? TextManager.FormatServerMessage(durationInfoTextId, new[] { "[infotext]", "[duration]" }, new[] { infoText, requiredDuration.ToString() }) : infoText;
}
private bool isCompleted = false;
@@ -12,7 +12,7 @@ namespace Barotrauma
private readonly string timeLimitInfoTextId;
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[timelimit]" });
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(timeLimit):g}" });
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)
{
@@ -20,7 +20,7 @@ namespace Barotrauma
return !string.IsNullOrEmpty(timeLimitInfoTextId) ? TextManager.FormatServerMessage(timeLimitInfoTextId, new[] { "[infotext]", "[timelimit]" }, new[] { infoText, $"{TimeSpan.FromSeconds(timeLimit):g}" }) : infoText;
}
public override bool CanBeCompleted => base.CanBeCompleted && (!IsStarted || timeRemaining > 0.0f);
public override bool CanBeCompleted(ICollection<Traitor> traitors) => base.CanBeCompleted(traitors) && (!Traitors.Any(IsStarted) || timeRemaining > 0.0f);
private float timeRemaining;
@@ -9,19 +9,17 @@ namespace Barotrauma
{
private readonly string optionalInfoTextId;
public override string StatusValueTextId => (base.IsStarted && !base.CanBeCompleted) ? "failed" : base.StatusValueTextId;
public override string StatusValueTextId => (Traitors.Any(IsStarted) && !base.CanBeCompleted(Traitors)) ? "failed" : base.StatusValueTextId;
public override IEnumerable<string> StatusTextValues
public override IEnumerable<string> StatusTextValues(Traitor traitor)
{
get {
var values = base.StatusTextValues.ToArray();
values[1] = TextManager.GetServerMessage(StatusValueTextId);
return values;
}
var values = base.StatusTextValues(traitor).ToArray();
values[1] = TextManager.GetServerMessage(StatusValueTextId);
return values;
}
public override bool IsCompleted => base.IsCompleted || (base.IsStarted && !base.CanBeCompleted);
public override bool CanBeCompleted => true;
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)
{
@@ -30,25 +30,25 @@ namespace Barotrauma
}
public override IEnumerable<string> StatusTextKeys => Goal.StatusTextKeys;
public override IEnumerable<string> StatusTextValues => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
public override IEnumerable<string> StatusTextValues(Traitor traitor) => new [] { InfoText(traitor), TextManager.FormatServerMessage(StatusValueTextId) };
public override IEnumerable<string> InfoTextKeys => Goal.InfoTextKeys;
public override IEnumerable<string> InfoTextValues => Goal.InfoTextValues;
public override IEnumerable<string> InfoTextValues(Traitor traitor) => Goal.InfoTextValues(traitor);
public override IEnumerable<string> CompletedTextKeys => Goal.CompletedTextKeys;
public override IEnumerable<string> CompletedTextValues => Goal.CompletedTextValues;
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 => GetStatusText(Traitor, StatusTextId, StatusTextKeys, StatusTextValues);
public override string InfoText => GetInfoText(Traitor, InfoTextId, InfoTextKeys, InfoTextValues);
public override string CompletedText => CompletedTextId != null ? GetCompletedText(Traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues) : StatusText;
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 => base.IsStarted && Goal.IsStarted;
public override bool CanBeCompleted => base.CanBeCompleted && Goal.CanBeCompleted;
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);
@@ -21,9 +21,13 @@ namespace Barotrauma
public bool IsCompleted => pendingGoals.Count <= 0;
public bool IsPartiallyCompleted => completedGoals.Count > 0;
public bool IsStarted { get; private set; } = false;
public bool CanBeCompleted => !IsStarted || pendingGoals.All(goal => goal.CanBeCompleted);
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; }
@@ -33,7 +37,7 @@ namespace Barotrauma
string.Join("/",
string.Join("/", activeGoals.Select((goal, index) =>
{
var statusText = goal.StatusText;
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, new string[] { "[statustext]" }, new string[] { $"[{index}.st]" })}";
}).ToArray()),
@@ -43,7 +47,7 @@ namespace Barotrauma
string.Join("/",
string.Join("/", allGoals.Select((goal, index) =>
{
var statusText = goal.StatusText;
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, new string[] { "[statustext]" }, new string[] { $"[{index}.st]" })}";
}).ToArray()),
@@ -127,28 +131,21 @@ namespace Barotrauma
}
IsStarted = true;
traitor.SendChatMessageBox(StartMessageText);
traitor.UpdateCurrentObjective(GoalInfos);
traitor.SendChatMessageBox(StartMessageText, traitor.Mission?.Identifier);
traitor.UpdateCurrentObjective(GoalInfos, traitor.Mission?.Identifier);
return true;
}
public void StartMessage()
{
Traitor.SendChatMessage(StartMessageText);
}
public void End(bool displayMessage)
{
if (displayMessage)
{
Traitor.SendChatMessageBox(EndMessageText);
}
Traitor.SendChatMessage(StartMessageText, Traitor.Mission?.Identifier);
}
public void EndMessage()
{
Traitor.SendChatMessage(EndMessageText);
Traitor.SendChatMessageBox(EndMessageText, Traitor.Mission?.Identifier);
Traitor.SendChatMessage(EndMessageText, Traitor.Mission?.Identifier);
}
public void Update(float deltaTime)
@@ -171,28 +168,24 @@ namespace Barotrauma
pendingGoals.RemoveAt(i);
if (GameMain.Server != null)
{
Traitor.SendChatMessage(goal.CompletedText);
Traitor.SendChatMessage(goal.CompletedText(Traitor), Traitor.Mission?.Identifier);
if (pendingGoals.Count > 0)
{
Traitor.SendChatMessageBox(goal.CompletedText);
Traitor.SendChatMessageBox(goal.CompletedText(Traitor), Traitor.Mission?.Identifier);
}
Traitor.UpdateCurrentObjective(GoalInfos);
Traitor.UpdateCurrentObjective(GoalInfos, Traitor.Mission?.Identifier);
}
}
}
}
public Objective(string infoText, int shuffleGoalsCount, params Goal[] goals)
public Objective(string infoText, int shuffleGoalsCount, ICollection<string> roles, ICollection<Goal> goals)
{
InfoText = infoText;
this.shuffleGoalsCount = shuffleGoalsCount;
Roles.UnionWith(roles);
allGoals.AddRange(goals);
}
public bool HasGoalsOfType<T>() where T : Goal
{
return allGoals?.Any(g => g is T) ?? false;
}
}
}
}
@@ -1,7 +1,4 @@
using Barotrauma.Networking;
using Lidgren.Network;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
@@ -9,8 +6,8 @@ namespace Barotrauma
{
public readonly Character Character;
public string Role { get; private set; }
public TraitorMission Mission { get; private set; }
public string Role { get; }
public TraitorMission Mission { get; }
public Objective CurrentObjective => Mission.GetCurrentObjective(this);
public Traitor(TraitorMission mission, string role, Character character)
@@ -30,37 +27,32 @@ namespace Barotrauma
}, new string[] {
codeWords, codeResponse
});
messageSender(greetingMessage);
// boxSender(greetingMessage);
// SendChatMessage(greetingMessage);
// SendChatMessageBox(greetingMessage);
Client traitorClient = server.ConnectedClients.Find(c => c.Character == Character);
Client ownerClient = server.ConnectedClients.Find(c => c.Connection == server.OwnerConnection);
if (traitorClient != ownerClient && ownerClient != null && ownerClient.Character == null)
{
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText, TraitorMessageType.ServerMessageBox);
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText, Mission?.Identifier, TraitorMessageType.ServerMessageBox);
}
}
public void SendChatMessage(string serverText)
public void SendChatMessage(string serverText, string iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, TraitorMessageType.Server);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, iconIdentifier, TraitorMessageType.Server);
}
public void SendChatMessageBox(string serverText)
public void SendChatMessageBox(string serverText, string iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, TraitorMessageType.ServerMessageBox);
GameMain.Server.SendTraitorMessage(traitorClient, serverText, iconIdentifier, TraitorMessageType.ServerMessageBox);
}
public void UpdateCurrentObjective(string objectiveText)
public void UpdateCurrentObjective(string objectiveText, string iconIdentifier)
{
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
Character.TraitorCurrentObjective = objectiveText;
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective, TraitorMessageType.Objective);
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective, iconIdentifier, TraitorMessageType.Objective);
}
}
}
@@ -24,6 +24,12 @@ namespace Barotrauma
private readonly Dictionary<ulong, int> traitorCountsBySteamId = new Dictionary<ulong, int>();
private readonly Dictionary<string, int> traitorCountsByEndPoint = new Dictionary<string, int>();
public bool ShouldEndRound
{
get;
set;
}
public int GetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint)
{
if (steamIdAndEndPoint.Item1 > 0 && traitorCountsBySteamId.TryGetValue(steamIdAndEndPoint.Item1, out var steamIdResult))
@@ -51,6 +57,16 @@ namespace Barotrauma
return Traitors.Any(traitor => traitor.Character == character);
}
public string GetTraitorRole(Character character)
{
var traitor = Traitors.FirstOrDefault(candidate => candidate.Character == character);
if (traitor == null)
{
return "";
}
return traitor.Role;
}
public TraitorManager()
{
}
@@ -60,18 +76,21 @@ namespace Barotrauma
#if DISABLE_MISSIONS
return;
#endif
if (server == null) return;
if (server == null) { return; }
ShouldEndRound = false;
Traitor.TraitorMission.InitializeRandom();
this.server = server;
//TODO: configure countdowns in xml
startCountdown = MathHelper.Lerp(90.0f, 180.0f, (float)Traitor.TraitorMission.RandomDouble());
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinStartDelay, server.ServerSettings.TraitorsMaxStartDelay, (float)Traitor.TraitorMission.RandomDouble());
traitorCountsBySteamId.Clear();
traitorCountsByEndPoint.Clear();
}
public void Update(float deltaTime)
{
if (ShouldEndRound) { return; }
#if DISABLE_MISSIONS
return;
#endif
@@ -102,21 +121,20 @@ namespace Barotrauma
missionCompleted = true;
foreach (var traitor in mission.Value.Traitors.Values)
{
traitor.UpdateCurrentObjective("");
traitor.UpdateCurrentObjective("", mission.Value.Identifier);
}
}
}
if (gameShouldEnd)
{
GameMain.GameSession.WinningTeam = winningTeam;
GameMain.Server.EndGame();
ShouldEndRound = true;
return;
}
if (missionCompleted)
{
Missions.Clear();
//TODO: configure countdowns in xml
startCountdown = MathHelper.Lerp(90.0f, 180.0f, (float)Traitor.TraitorMission.RandomDouble());
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)Traitor.TraitorMission.RandomDouble());
}
}
else if (startCountdown > 0.0f && server.GameStarted)
@@ -127,7 +145,7 @@ namespace Barotrauma
int playerCharactersCount = server.ConnectedClients.Sum(client => client.Character != null && !client.Character.IsDead ? 1 : 0);
if (playerCharactersCount < server.ServerSettings.TraitorsMinPlayerCount)
{
startCountdown = 60.0f;
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)Traitor.TraitorMission.RandomDouble());
return;
}
if (GameMain.GameSession.Mission is CombatMission)
@@ -141,10 +159,10 @@ namespace Barotrauma
Missions.Add(teamId, mission);
}
}
var canBeStartedCount = Missions.Sum(mission => mission.Value.CanBeStarted(server, this, mission.Key, "traitor") ? 1 : 0);
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, "traitor") ? 1 : 0);
var startSuccessCount = Missions.Sum(mission => mission.Value.Start(server, this, mission.Key) ? 1 : 0);
if (startSuccessCount >= Missions.Count)
{
return;
@@ -155,9 +173,9 @@ namespace Barotrauma
{
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
if (mission != null) {
if (mission.CanBeStarted(server, this, Character.TeamType.None, "traitor"))
if (mission.CanBeStarted(server, this, Character.TeamType.None))
{
if (mission.Start(server, this, Character.TeamType.None, "traitor"))
if (mission.Start(server, this, Character.TeamType.None))
{
Missions.Add(Character.TeamType.None, mission);
return;
@@ -166,7 +184,7 @@ namespace Barotrauma
}
}
Missions.Clear();
startCountdown = 60.0f;
startCountdown = MathHelper.Lerp(server.ServerSettings.TraitorsMinRestartDelay, server.ServerSettings.TraitorsMaxRestartDelay, (float)Traitor.TraitorMission.RandomDouble());
}
}
}
@@ -178,23 +196,31 @@ namespace Barotrauma
#endif
if (GameMain.Server == null || !Missions.Any()) return "";
return string.Join("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage));
return TextManager.JoinServerMessages("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage).ToArray());
}
public static T WeightedRandom<T>(ICollection<T> collection, Func<int, int> random, Func<T, int> readSelectedWeight, Action<T, int> writeSelectedWeight, int entryWeight, int selectionWeight) where T : class
public static T WeightedRandom<T>(IList<T> collection, int startIndex, int count, Func<int, int> random, Func<T, int> readSelectedWeight, Action<T, int> writeSelectedWeight, int entryWeight, int selectionWeight) where T : class
{
var count = collection.Count;
if (count <= 0)
{
return null;
}
var maxCount = entryWeight + collection.Max(readSelectedWeight);
var totalWeight = collection.Sum(entry => maxCount - readSelectedWeight(entry));
var selected = random(totalWeight);
foreach (var entry in collection)
var maxWeight = readSelectedWeight(collection[startIndex]);
var totalWeight = entryWeight + maxWeight;
for (var i = 1; i < count; ++i)
{
var weight = readSelectedWeight(collection[startIndex + i]);
maxWeight = Math.Max(maxWeight, weight);
totalWeight += weight;
}
maxWeight += entryWeight;
totalWeight = count * maxWeight - totalWeight;
var selected = random(totalWeight);
for(var i = 0; i < count; ++i)
{
var entry = collection[startIndex + i];
var weight = readSelectedWeight(entry);
selected -= maxCount;
selected -= maxWeight;
selected += weight;
if (selected <= 0)
{
@@ -204,5 +230,10 @@ namespace Barotrauma
}
return null;
}
public static T WeightedRandom<T>(IList<T> collection, Func<int, int> random, Func<T, int> readSelectedWeight, Action<T, int> writeSelectedWeight, int entryWeight, int selectionWeight) where T : class
{
return WeightedRandom<T>(collection, 0, collection.Count, random, readSelectedWeight, writeSelectedWeight, entryWeight, selectionWeight);
}
}
}
@@ -1,5 +1,5 @@
//#define SERVER_IS_TRAITOR
//#define ALLOW_SOLO_TRAITOR
//#define ALLOW_SOLO_TRAITOR
//#define ALLOW_NONHUMANOID_TRAITOR
using System;
using Barotrauma.Networking;
@@ -7,6 +7,7 @@ using Lidgren.Network;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using Barotrauma.Extensions;
namespace Barotrauma
@@ -35,28 +36,12 @@ namespace Barotrauma
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 EndMessage {
get
{
if (!Traitors.TryGetValue("traitor", out Traitor traitor))
{
return "";
}
if (pendingObjectives.Count <= 0)
{
if (completedObjectives.Count <= 0) return "";
return completedObjectives[completedObjectives.Count - 1].EndMessageText;
}
else
{
return pendingObjectives[0].EndMessageText;
}
}
}
public string GlobalEndMessageSuccessTextId { get; private set; }
public string GlobalEndMessageSuccessDeadTextId { get; private set; }
@@ -65,14 +50,14 @@ namespace Barotrauma
public string GlobalEndMessageFailureDeadTextId { get; private set; }
public string GlobalEndMessageFailureDetainedTextId { get; private set; }
private readonly string objectiveGoalInfoFormat = "[index]. [goalinfos]\n";
public readonly string Identifier;
public virtual IEnumerable<string> GlobalEndMessageKeys => new string[] { "[traitorname]", "[traitorgoalinfos]" };
public virtual IEnumerable<string> GlobalEndMessageValues {
get {
var isSuccess = completedObjectives.Count >= allObjectives.Count;
return new string[] {
(Traitors.TryGetValue("traitor", out var traitor) ? traitor.Character?.Name : null) ?? "(unknown)",
string.Join(", ", Traitors.Values.Select(traitor => traitor.Character?.Name ?? "(unknown)")),
(isSuccess ? completedObjectives.LastOrDefault() : pendingObjectives.FirstOrDefault())?.GoalInfos ?? ""
};
}
@@ -82,20 +67,19 @@ namespace Barotrauma
{
get
{
if (!Traitors.TryGetValue("traitor", out Traitor traitor))
if (Traitors.Any() && allObjectives.Count > 0)
{
return "";
}
if (allObjectives.Count > 0)
{
var isSuccess = completedObjectives.Count >= allObjectives.Count;
var traitorIsDead = traitor.Character.IsDead;
var traitorIsDetained = traitor.Character.LockHands;
var messageId = isSuccess
? (traitorIsDead ? GlobalEndMessageSuccessDeadTextId : traitorIsDetained ? GlobalEndMessageSuccessDetainedTextId : GlobalEndMessageSuccessTextId)
: (traitorIsDead ? GlobalEndMessageFailureDeadTextId : traitorIsDetained ? GlobalEndMessageFailureDetainedTextId : GlobalEndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(traitor.Character?.Info?.Gender ?? Gender.None, messageId, GlobalEndMessageKeys.ToArray(), GlobalEndMessageValues.ToArray());
return TextManager.JoinServerMessages("\n",
Traitors.Values.Select(traitor =>
{
var isSuccess = completedObjectives.Count >= allObjectives.Count;
var traitorIsDead = traitor.Character.IsDead;
var traitorIsDetained = traitor.Character.LockHands;
var messageId = isSuccess
? (traitorIsDead ? GlobalEndMessageSuccessDeadTextId : traitorIsDetained ? GlobalEndMessageSuccessDetainedTextId : GlobalEndMessageSuccessTextId)
: (traitorIsDead ? GlobalEndMessageFailureDeadTextId : traitorIsDetained ? GlobalEndMessageFailureDetainedTextId : GlobalEndMessageFailureTextId);
return TextManager.FormatServerMessageWithGenderPronouns(traitor.Character?.Info?.Gender ?? Gender.None, messageId, GlobalEndMessageKeys.ToArray(), GlobalEndMessageValues.ToArray());
}).ToArray());
}
return "";
}
@@ -103,21 +87,27 @@ namespace Barotrauma
public Objective GetCurrentObjective(Traitor traitor)
{
return pendingObjectives.Count > 0 ? pendingObjectives[0] : null;
if (!Traitors.ContainsValue(traitor) || pendingObjectives.Count <= 0)
{
return null;
}
return pendingObjectives.Find(objective => objective.Roles.Contains(traitor.Role));
}
protected List<Tuple<Client, Character>> FindTraitorCandidates(GameServer server, Character.TeamType team, params string[] traitorRoles)
protected List<Tuple<Client, Character>> FindTraitorCandidates(GameServer server, Character.TeamType team, RoleFilter traitorRoleFilter)
{
var traitorCandidates = new List<Tuple<Client, Character>>();
#if SERVER_IS_TRAITOR
if (server.Character != null)
foreach (Client c in server.ConnectedClients)
{
traitorCandidates.Add(server.Character);
}
else
if (c.Character == null || c.Character.IsDead || c.Character.Removed || !traitorRoleFilter(c.Character) ||
(team != Character.TeamType.None && c.Character.TeamID != team))
{
continue;
}
#if !ALLOW_NONHUMANOID_TRAITOR
if (!c.Character.IsHumanoid) { continue; }
#endif
{
traitorCandidates.AddRange(server.ConnectedClients.FindAll(c => c.Character != null && !c.Character.IsDead && (team == Character.TeamType.None || c.Character.TeamID == team)).ConvertAll(client => Tuple.Create(client, client.Character)));
traitorCandidates.Add(Tuple.Create(c, c.Character));
}
return traitorCandidates;
}
@@ -132,73 +122,127 @@ namespace Barotrauma
return characters;
}
public virtual bool CanBeStarted(GameServer server, TraitorManager traitorManager, Character.TeamType team, params string[] traitorRoles)
{
var traitorCandidates = FindTraitorCandidates(server, team, traitorRoles);
if (traitorCandidates.Count <= 0)
{
return false;
}
var characters = FindCharacters();
#if !ALLOW_SOLO_TRAITOR
if (characters.Count < 2)
{
return false;
}
#endif
return true;
}
public virtual bool Start(GameServer server, TraitorManager traitorManager, Character.TeamType team, params string[] traitorRoles)
protected List<Tuple<string, Tuple<Client, Character>>> AssignTraitors(GameServer server, TraitorManager traitorManager, Character.TeamType team)
{
List<Character> characters = FindCharacters();
List<Tuple<Client, Character>> traitorCandidates = FindTraitorCandidates(server, team, traitorRoles);
if (traitorCandidates.Count <= 0)
{
return false;
}
#if !ALLOW_SOLO_TRAITOR
if (characters.Count < 2)
{
return false;
return null;
}
#endif
CodeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
CodeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
Traitors.Clear();
foreach (var role in traitorRoles)
var roleCandidates = new Dictionary<string, HashSet<Tuple<Client, Character>>>();
foreach (var role in Roles)
{
var candidate = TraitorManager.WeightedRandom(traitorCandidates, Random, t =>
roleCandidates.Add(role.Key, new HashSet<Tuple<Client, Character>>(FindTraitorCandidates(server, team, role.Value)));
if (roleCandidates[role.Key].Count <= 0)
{
return null;
}
}
var candidateRoleCounts = new Dictionary<Tuple<Client, Character>, int>();
foreach (var candidateEntry in roleCandidates)
{
foreach (var candidate in candidateEntry.Value)
{
candidateRoleCounts[candidate] = candidateRoleCounts.TryGetValue(candidate, out var count) ? count + 1 : 1;
}
}
var unassignedRoles = new List<string>(roleCandidates.Keys);
unassignedRoles.Sort((a, b) => roleCandidates[a].Count - roleCandidates[b].Count);
var assignedCandidates = new List<Tuple<string, Tuple<Client, Character>>>();
while (unassignedRoles.Count > 0)
{
var currentRole = unassignedRoles[0];
var availableCandidates = roleCandidates[currentRole].ToList();
if (availableCandidates.Count <= 0)
{
break;
}
unassignedRoles.RemoveAt(0);
availableCandidates.Sort((a, b) => candidateRoleCounts[b] - candidateRoleCounts[a]);
unassignedRoles.Sort((a, b) => roleCandidates[a].Count - roleCandidates[b].Count);
int numCandidates = 1;
for (int i = 1; i < availableCandidates.Count && candidateRoleCounts[availableCandidates[i]] == candidateRoleCounts[availableCandidates[0]]; ++i)
{
++numCandidates;
}
var selected = TraitorManager.WeightedRandom(availableCandidates, 0, numCandidates, Random, t =>
{
var previousClient = server.FindPreviousClientData(t.Item1);
return Math.Max(
previousClient != null ? traitorManager.GetTraitorCount(previousClient) : 0,
traitorManager.GetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? "")));
}, (t, c) =>
{
traitorManager.SetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? ""), c);
}, 2, 3);
traitorCandidates.Remove(candidate);
}, (t, c) => { traitorManager.SetTraitorCount(Tuple.Create(t.Item1.SteamID, t.Item1.Connection?.EndPointString ?? ""), c); }, 2, 3);
var traitor = new Traitor(this, role, candidate.Item2);
Traitors.Add(role, traitor);
assignedCandidates.Add(Tuple.Create(currentRole, selected));
foreach (var candidate in roleCandidates.Values)
{
candidate.Remove(selected);
}
}
if (unassignedRoles.Count > 0)
{
return null;
}
return assignedCandidates;
}
public virtual bool CanBeStarted(GameServer server, TraitorManager traitorManager, Character.TeamType team)
{
foreach (var role in Roles)
{
var candidates = FindTraitorCandidates(server, team, role.Value);
if (candidates.Count <= 0)
{
return false;
}
}
return AssignTraitors(server, traitorManager, team) != null;
}
public virtual bool Start(GameServer server, TraitorManager traitorManager, Character.TeamType team)
{
var assignedCandidates = AssignTraitors(server, traitorManager, team);
if (assignedCandidates == null)
{
return false;
}
var messages = new Dictionary<Traitor, List<string>>();
Traitors.Clear();
foreach (var candidate in assignedCandidates)
{
var traitor = new Traitor(this, candidate.Item1, candidate.Item2.Item1.Character);
Traitors.Add(candidate.Item1, traitor);
}
CodeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
CodeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
if (pendingObjectives.Count <= 0 || !pendingObjectives[0].CanBeStarted(Traitors.Values))
{
Traitors.Clear();
return false;
}
var pendingMessages = new Dictionary<Traitor, List<string>>();
pendingMessages.Clear();
foreach (var traitor in Traitors.Values)
{
messages[traitor] = new List<string>();
if (traitor.CurrentObjective == null) { continue; }
traitor.Greet(server, CodeWords, CodeResponse, message => messages[traitor].Add(message));
pendingMessages.Add(traitor, new List<string>());
}
foreach (var traitor in Traitors.Values)
{
traitor.Greet(server, CodeWords, CodeResponse, message => pendingMessages[traitor].Add(message));
}
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message, Identifier)));
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message, Identifier)));
messages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message)));
Update(0.0f, GameMain.Server.EndGame);
messages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message)));
Update(0.0f, () => { GameMain.Server.TraitorManager.ShouldEndRound = true; });
#if SERVER
foreach (var traitor in Traitors.Values)
{
GameServer.Log(string.Format("{0} is the traitor and the current goals are:\n{1}", traitor.Character.Name, traitor.CurrentObjective?.GoalInfos != null ? TextManager.GetServerMessage(traitor.CurrentObjective?.GoalInfos) : "(empty)"), ServerLog.MessageType.ServerMessage);
GameServer.Log($"{traitor.Character.Name} is a traitor and the current goals are:\n{(traitor.CurrentObjective?.GoalInfos != null ? TextManager.GetServerMessage(traitor.CurrentObjective?.GoalInfos) : "(empty)")}", ServerLog.MessageType.ServerMessage);
}
#endif
return true;
@@ -212,23 +256,41 @@ namespace Barotrauma
{
return;
}
if (Traitors.Values.Any(traitor => traitor.Character?.IsDead ?? true))
{
Traitors.Values.ForEach(traitor => traitor.UpdateCurrentObjective("", Identifier));
return;
}
var startedObjectives = new List<Objective>();
foreach (var traitor in Traitors.Values)
{
if (traitor.Character.IsDead)
startedObjectives.Clear();
while (pendingObjectives.Count > 0)
{
traitor.UpdateCurrentObjective("");
}
}
int previousCompletedCount = completedObjectives.Count;
int startedCount = 0;
while (pendingObjectives.Count > 0)
{
var objective = pendingObjectives[0];
if (!objective.IsStarted)
{
if (!objective.Start(Traitors["traitor"]))
var objective = GetCurrentObjective(traitor);
if (objective == null)
{
pendingObjectives.RemoveAt(0);
// No more objectives left for traitor or waiting for another traitor's objective.
break;
}
if (!objective.IsStarted)
{
if (!objective.Start(traitor))
{
//the mission fails if an objective cannot be started
if (completedObjectives.Count > 0)
{
objective.EndMessage();
}
pendingObjectives.Clear();
break;
}
startedObjectives.Add(objective);
}
objective.Update(deltaTime);
if (objective.IsCompleted)
{
pendingObjectives.Remove(objective);
completedObjectives.Add(objective);
if (pendingObjectives.Count > 0)
{
@@ -236,41 +298,19 @@ namespace Barotrauma
}
continue;
}
++startedCount;
}
objective.Update(deltaTime);
if (objective.IsCompleted)
{
pendingObjectives.RemoveAt(0);
completedObjectives.Add(objective);
if (pendingObjectives.Count > 0)
if (objective.IsStarted && !objective.CanBeCompleted)
{
objective.EndMessage();
pendingObjectives.Clear();
}
continue;
break;
}
if (!objective.CanBeCompleted)
if (pendingObjectives.Count > 0)
{
objective.EndMessage();
objective.End(true);
pendingObjectives.Clear();
}
break;
}
int completedMax = completedObjectives.Count - 1;
for (int i = previousCompletedCount; i <= completedMax; ++i)
{
var objective = completedObjectives[i];
objective.End(i < completedMax || pendingObjectives.Count > 0);
}
if (pendingObjectives.Count > 0)
{
if (startedCount > 0)
{
pendingObjectives[0].StartMessage();
startedObjectives.ForEach(objective => objective.StartMessage());
}
}
else if (completedObjectives.Count >= allObjectives.Count)
if (completedObjectives.Count >= allObjectives.Count)
{
foreach (var traitor in Traitors)
{
@@ -303,8 +343,9 @@ namespace Barotrauma
#endif
}
public TraitorMission(string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, params Objective[] objectives)
public TraitorMission(string identifier, string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, IEnumerable<KeyValuePair<string, RoleFilter>> roles, ICollection<Objective> objectives)
{
Identifier = identifier;
StartText = startText;
GlobalEndMessageSuccessTextId = globalEndMessageSuccessTextId;
GlobalEndMessageSuccessDeadTextId = globalEndMessageSuccessDeadTextId;
@@ -312,6 +353,10 @@ namespace Barotrauma
GlobalEndMessageFailureTextId = globalEndMessageFailureTextId;
GlobalEndMessageFailureDeadTextId = globalEndMessageFailureDeadTextId;
GlobalEndMessageFailureDetainedTextId = globalEndMessageFailureDetainedTextId;
foreach (var role in roles)
{
Roles.Add(role.Key, role.Value);
}
allObjectives.AddRange(objectives);
pendingObjectives.AddRange(objectives);
}
@@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma {
namespace Barotrauma
{
class TraitorMissionPrefab
{
public class TraitorMissionEntry
@@ -98,6 +98,7 @@ namespace Barotrauma {
private static Dictionary<string, TargetFilter> targetFilters = new Dictionary<string, TargetFilter>()
{
{ "job", (value, character) => value.Equals(character.Info.Job.Prefab.Identifier, StringComparison.OrdinalIgnoreCase) },
{ "role", (value, character) => value.Equals(GameMain.Server.TraitorManager.GetTraitorRole(character), StringComparison.OrdinalIgnoreCase) }
};
public Traitor.Goal Instantiate()
@@ -256,7 +257,16 @@ namespace Barotrauma {
}
}
public class Objective
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; }
@@ -271,16 +281,24 @@ namespace Barotrauma {
public readonly List<Goal> Goals = new List<Goal>();
public Traitor.Objective Instantiate()
private List<Traitor.Goal> goalInstances = null;
public override void InstantiateGoals()
{
var result = new Traitor.Objective(InfoText, ShuffleGoalsCount, Goals.ConvertAll(goal => {
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).ToArray());
}).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;
@@ -316,14 +334,43 @@ namespace Barotrauma {
return result;
}
}
/*
public class Role
protected class WaitObjective : ObjectiveBase
{
public string Job;
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 string Identifier;
public readonly string StartText;
public readonly string EndMessageSuccessText;
@@ -333,11 +380,43 @@ namespace Barotrauma {
public readonly string EndMessageFailureDeadText;
public readonly string EndMessageFailureDetainedText;
public readonly List<Objective> Objectives = new List<Objective>();
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",
@@ -345,7 +424,12 @@ namespace Barotrauma {
EndMessageFailureText ?? "TraitorObjectiveEndMessageFailure",
EndMessageFailureDeadText ?? "TraitorObjectiveEndMessageFailureDead",
EndMessageFailureDetainedText ?? "TraitorObjectiveEndMessageFailureDetained",
Objectives.ConvertAll(objective => objective.Instantiate()).ToArray());
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)
@@ -354,10 +438,22 @@ namespace Barotrauma {
return new Goal(goalType, goalRoot);
}
protected Objective LoadObjective(XElement objectiveRoot)
{
var result = new Objective();
result.ShuffleGoalsCount = objectiveRoot.GetAttributeInt("shuffleGoalsCount", -1);
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))
@@ -410,7 +506,7 @@ namespace Barotrauma {
break;
}
default:
GameServer.Log($"Unrecognized element \"{element.Name}\"under Objective.", ServerLog.MessageType.Error);
GameServer.Log($"Unrecognized element \"{element.Name}\" under Objective.", ServerLog.MessageType.Error);
break;
}
}
@@ -418,6 +514,18 @@ namespace Barotrauma {
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(CultureInfo.InvariantCulture)));
}
return new Role(filters);
}
public TraitorMissionPrefab(XElement missionRoot)
{
Identifier = missionRoot.GetAttributeString("identifier", null);
@@ -427,6 +535,27 @@ namespace Barotrauma {
{
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);
@@ -457,7 +586,7 @@ namespace Barotrauma {
break;
case "objective":
{
var objective = LoadObjective(element);
var objective = LoadObjective(element, Roles.Keys.ToArray());
if (objective != null)
{
Objectives.Add(objective);