(a00338777) v0.9.2.1
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.SqlServer.Server;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public abstract class Goal
|
||||
{
|
||||
public Traitor Traitor { get; private set; }
|
||||
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 => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
|
||||
|
||||
public virtual IEnumerable<string> InfoTextKeys => new string[] { };
|
||||
public virtual IEnumerable<string> InfoTextValues => new string[] { };
|
||||
|
||||
public virtual IEnumerable<string> CompletedTextKeys => new string[] { };
|
||||
public virtual IEnumerable<string> CompletedTextValues => 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);
|
||||
|
||||
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 => GetStatusText(Traitor, StatusTextId, StatusTextKeys, StatusTextValues);
|
||||
public virtual string InfoText => GetInfoText(Traitor, InfoTextId, InfoTextKeys, InfoTextValues);
|
||||
|
||||
public virtual string CompletedText => CompletedTextId != null ? GetCompletedText(Traitor, CompletedTextId, CompletedTextKeys, CompletedTextValues) : StatusText;
|
||||
|
||||
public abstract bool IsCompleted { get; }
|
||||
public virtual bool IsStarted => Traitor != null;
|
||||
public virtual bool CanBeCompleted => !(Traitor?.Character?.IsDead ?? true);
|
||||
|
||||
public virtual bool IsEnemy(Character character) => false;
|
||||
|
||||
public virtual bool Start(Traitor traitor)
|
||||
{
|
||||
Traitor = traitor;
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void Update(float deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
protected Goal()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
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 => base.InfoTextValues.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 && item.FindParentInventory(inventory => inventory.Owner is Character && inventory.Owner != Traitor.Character) != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.Submarine == null)
|
||||
{
|
||||
if (!(item.ParentInventory?.Owner is Character)) { continue; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (item.Submarine.TeamID != Traitor.Character.TeamID) { continue; }
|
||||
}
|
||||
|
||||
if (item.Condition <= 0.0f)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var identifierMatches = matchIdentifier && item.prefab.Identifier == tag;
|
||||
if (identifierMatches && tagPrefabName == null)
|
||||
{
|
||||
var textId = item.Prefab.GetItemNameTextId();
|
||||
tagPrefabName = textId != null ? TextManager.FormatServerMessage(textId) : item.Prefab.Name;
|
||||
}
|
||||
if (identifierMatches || (matchTag && item.HasTag(tag)))
|
||||
{
|
||||
++result;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public class GoalFindItem : HumanoidGoal
|
||||
{
|
||||
private readonly string identifier;
|
||||
private readonly bool preferNew;
|
||||
private readonly bool allowNew;
|
||||
private readonly bool allowExisting;
|
||||
private readonly HashSet<string> allowedContainerIdentifiers = new HashSet<string>();
|
||||
|
||||
private ItemPrefab targetPrefab;
|
||||
private Item targetContainer;
|
||||
private Item target;
|
||||
private HashSet<Item> existingItems = new HashSet<Item>();
|
||||
private string targetNameText;
|
||||
private string targetContainerNameText;
|
||||
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 bool IsCompleted => target != null && target.ParentInventory == Traitor.Character.Inventory;
|
||||
public override bool CanBeCompleted {
|
||||
get
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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.Find(prefab => prefab is ItemPrefab && prefab.Identifier == identifier);
|
||||
}
|
||||
|
||||
protected Item FindRandomContainer(bool includeNew, bool includeExisting)
|
||||
{
|
||||
int itemsCount = Item.ItemList.Count;
|
||||
int startIndex = TraitorMission.Random(itemsCount);
|
||||
Item fallback = null;
|
||||
for (int i = 0; i < itemsCount; ++i)
|
||||
{
|
||||
var item = Item.ItemList[(i + startIndex) % itemsCount];
|
||||
if (item.Submarine == null || 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))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
targetPrefab = FindItemPrefab(identifier);
|
||||
if (targetPrefab == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (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;
|
||||
targetHullNameText = targetHullTextId != null ? TextManager.FormatServerMessage(targetHullTextId) : targetContainer?.CurrentHull?.DisplayName ?? "";
|
||||
if (allowNew && !targetContainer.OwnInventory.IsFull())
|
||||
{
|
||||
existingItems.Clear();
|
||||
foreach (var item in targetContainer.OwnInventory.Items)
|
||||
{
|
||||
existingItems.Add(item);
|
||||
}
|
||||
Entity.Spawner.AddToSpawnQueue(targetPrefab, targetContainer.OwnInventory);
|
||||
target = null;
|
||||
}
|
||||
else if (allowExisting)
|
||||
{
|
||||
target = targetContainer.OwnInventory.FindItemByIdentifier(targetPrefab.Identifier);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
if (target == null)
|
||||
{
|
||||
target = targetContainer.OwnInventory.Items.FirstOrDefault(item => item != null && item.Prefab.Identifier == identifier && !existingItems.Contains(item));
|
||||
if (target != null)
|
||||
{
|
||||
existingItems.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public GoalFindItem(string identifier, bool preferNew, bool allowNew, bool allowExisting, params string[] allowedContainerIdentifiers)
|
||||
{
|
||||
this.identifier = identifier;
|
||||
this.preferNew = preferNew;
|
||||
this.allowNew = allowNew;
|
||||
this.allowExisting = allowExisting;
|
||||
this.allowedContainerIdentifiers.UnionWith(allowedContainerIdentifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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 => base.InfoTextValues.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.IsOutpost || hull.Submarine.TeamID != Traitor.Character.TeamID) { 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
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 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)" });
|
||||
|
||||
private bool isCompleted = false;
|
||||
public override bool IsCompleted => isCompleted;
|
||||
|
||||
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && character == Target);
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
isCompleted = Target?.IsDead ?? false;
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Target = traitor.Mission.FindKillTarget(traitor.Character, Filter);
|
||||
return Target != null && !Target.IsDead;
|
||||
}
|
||||
|
||||
public GoalKillTarget(TraitorMission.CharacterFilter filter) : base()
|
||||
{
|
||||
InfoTextId = "TraitorGoalKillTargetInfo";
|
||||
Filter = filter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public class GoalRandom : Goal
|
||||
{
|
||||
private readonly List<Goal> allGoals;
|
||||
|
||||
private readonly List<Goal> selectedGoals = new List<Goal>();
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => base.InfoTextKeys.Concat(new string[] { "[targetname]" });
|
||||
public override IEnumerable<string> InfoTextValues => base.InfoTextValues.Concat(new string[] { Target?.Name ?? "(unknown)" });
|
||||
|
||||
private bool isCompleted = false;
|
||||
public override bool IsCompleted => isCompleted;
|
||||
|
||||
public override bool IsEnemy(Character character) => base.IsEnemy(character) || (!isCompleted && character == Target);
|
||||
|
||||
public override void Update(float deltaTime)
|
||||
{
|
||||
base.Update(deltaTime);
|
||||
isCompleted = Target?.IsDead ?? false;
|
||||
}
|
||||
|
||||
public override bool Start(Traitor traitor)
|
||||
{
|
||||
if (!base.Start(traitor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Target = traitor.Mission.FindKillTarget(traitor.Character, Filter);
|
||||
return Target != null && !Target.IsDead;
|
||||
}
|
||||
|
||||
public GoalRandom(params Goal[] goals, int count)
|
||||
{
|
||||
this.goals = goals;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
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;
|
||||
|
||||
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 bool IsCompleted
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Traitor == null || Traitor.Character == null || 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;
|
||||
}
|
||||
}
|
||||
|
||||
public GoalReachDistanceFromSub(float requiredDistance) : base()
|
||||
{
|
||||
InfoTextId = "TraitorGoalReachDistanceFromSub";
|
||||
this.requiredDistance = requiredDistance;
|
||||
requiredDistanceSqr = requiredDistance * requiredDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public class GoalReplaceInventory : HumanoidGoal
|
||||
{
|
||||
private readonly HashSet<string> sabotageContainerIds = new HashSet<string>();
|
||||
private readonly HashSet<string> validReplacementIds = new HashSet<string>();
|
||||
|
||||
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 => base.StatusTextValues.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 || item.Submarine.TeamID != Traitor.Character.TeamID)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (item.FindParentInventory(inventory => inventory.Owner is Character) != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (sabotageContainerIds.Contains(item.prefab.Identifier))
|
||||
{
|
||||
++totalAmount;
|
||||
if (item.OwnInventory.Items.Length <= 0 || item.OwnInventory.Items.All(containedItem => containedItem != null && !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(string[] containerIds, string[] replacementIds, float replaceAmount)
|
||||
{
|
||||
sabotageContainerIds.UnionWith(containerIds);
|
||||
validReplacementIds.UnionWith(replacementIds);
|
||||
this.replaceAmount = replaceAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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 => base.InfoTextValues.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 || item.Submarine.TeamID != Traitor.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;
|
||||
}
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 => base.InfoTextValues.Concat(new string[] { $"{TimeSpan.FromSeconds(requiredDuration):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(durationInfoTextId) ? TextManager.FormatServerMessage(durationInfoTextId, new[] { "[infotext]", "[duration]" }, new[] { infoText, $"{TimeSpan.FromSeconds(requiredDuration):g}" }) : 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
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 => base.InfoTextValues.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, new[] { "[infotext]", "[timelimit]" }, new[] { infoText, $"{TimeSpan.FromSeconds(timeLimit):g}" }) : infoText;
|
||||
}
|
||||
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && (!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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
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 => (base.IsStarted && !base.CanBeCompleted) ? "failed" : base.StatusValueTextId;
|
||||
|
||||
public override IEnumerable<string> StatusTextValues
|
||||
{
|
||||
get {
|
||||
var values = base.StatusTextValues.ToArray();
|
||||
values[1] = TextManager.GetServerMessage(StatusValueTextId);
|
||||
return values;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsCompleted => base.IsCompleted || (base.IsStarted && !base.CanBeCompleted);
|
||||
public override bool CanBeCompleted => 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, new[] { "[infotext]" }, new[] { infoText }) : infoText;
|
||||
}
|
||||
|
||||
public GoalIsOptional(Goal goal, string optionalInfoTextId) : base(goal)
|
||||
{
|
||||
this.optionalInfoTextId = optionalInfoTextId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
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 => new [] { InfoText, TextManager.FormatServerMessage(StatusValueTextId) };
|
||||
|
||||
public override IEnumerable<string> InfoTextKeys => Goal.InfoTextKeys;
|
||||
public override IEnumerable<string> InfoTextValues => Goal.InfoTextValues;
|
||||
|
||||
public override IEnumerable<string> CompletedTextKeys => Goal.CompletedTextKeys;
|
||||
public override IEnumerable<string> CompletedTextValues => Goal.CompletedTextValues;
|
||||
|
||||
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 bool IsCompleted => Goal.IsCompleted;
|
||||
public override bool IsStarted => base.IsStarted && Goal.IsStarted;
|
||||
public override bool CanBeCompleted => base.CanBeCompleted && Goal.CanBeCompleted;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
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 CanBeCompleted => !IsStarted || pendingGoals.All(goal => goal.CanBeCompleted);
|
||||
|
||||
public bool IsEnemy(Character character) => pendingGoals.Any(goal => goal.IsEnemy(character));
|
||||
|
||||
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;
|
||||
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()),
|
||||
string.Join("", activeGoals.Select((goal, index) => $"[{index}.sl]").ToArray()));
|
||||
|
||||
public string AllGoalInfos =>
|
||||
string.Join("/",
|
||||
string.Join("/", allGoals.Select((goal, index) =>
|
||||
{
|
||||
var statusText = goal.StatusText;
|
||||
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()),
|
||||
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 string StartMessageText => TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, StartMessageTextId, StartMessageKeys, StartMessageValues);
|
||||
|
||||
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 string StartMessageServerText => TextManager.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, StartMessageServerTextId, StartMessageServerKeys, StartMessageServerValues);
|
||||
|
||||
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.FormatServerMessageWithGenderPronouns(Traitor?.Character?.Info?.Gender ?? Gender.None, messageId, EndMessageKeys.ToArray(), EndMessageValues.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 = TraitorMission.Random(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)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
IsStarted = true;
|
||||
|
||||
traitor.SendChatMessageBox(StartMessageText);
|
||||
traitor.UpdateCurrentObjective(GoalInfos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void StartMessage()
|
||||
{
|
||||
Traitor.SendChatMessage(StartMessageText);
|
||||
}
|
||||
|
||||
public void End(bool displayMessage)
|
||||
{
|
||||
if (displayMessage)
|
||||
{
|
||||
Traitor.SendChatMessageBox(EndMessageText);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndMessage()
|
||||
{
|
||||
Traitor.SendChatMessage(EndMessageText);
|
||||
}
|
||||
|
||||
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);
|
||||
if (pendingGoals.Count > 0)
|
||||
{
|
||||
Traitor.SendChatMessageBox(goal.CompletedText);
|
||||
}
|
||||
Traitor.UpdateCurrentObjective(GoalInfos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Objective(string infoText, int shuffleGoalsCount, params Goal[] goals)
|
||||
{
|
||||
InfoText = infoText;
|
||||
this.shuffleGoalsCount = shuffleGoalsCount;
|
||||
allGoals.AddRange(goals);
|
||||
}
|
||||
|
||||
public bool HasGoalsOfType<T>() where T : Goal
|
||||
{
|
||||
return allGoals?.Any(g => g is T) ?? false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public readonly Character Character;
|
||||
|
||||
public string Role { get; private set; }
|
||||
public TraitorMission Mission { get; private set; }
|
||||
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 object[] { NetEntityEvent.Type.Status });
|
||||
}
|
||||
|
||||
public delegate void MessageSender(string message);
|
||||
public void Greet(GameServer server, string codeWords, string codeResponse, MessageSender messageSender)
|
||||
{
|
||||
string greetingMessage = TextManager.FormatServerMessage(Mission.StartText, new string[] {
|
||||
"[codewords]", "[coderesponse]"
|
||||
}, 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);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendChatMessage(string serverText)
|
||||
{
|
||||
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
|
||||
GameMain.Server.SendTraitorMessage(traitorClient, serverText, TraitorMessageType.Server);
|
||||
}
|
||||
|
||||
public void SendChatMessageBox(string serverText)
|
||||
{
|
||||
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
|
||||
GameMain.Server.SendTraitorMessage(traitorClient, serverText, TraitorMessageType.ServerMessageBox);
|
||||
}
|
||||
|
||||
public void UpdateCurrentObjective(string objectiveText)
|
||||
{
|
||||
Client traitorClient = GameMain.Server.ConnectedClients.Find(c => c.Character == Character);
|
||||
Character.TraitorCurrentObjective = objectiveText;
|
||||
GameMain.Server.SendTraitorMessage(traitorClient, Character.TraitorCurrentObjective, TraitorMessageType.Objective);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
// #define DISABLE_MISSIONS
|
||||
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class TraitorManager
|
||||
{
|
||||
public readonly Dictionary<Character.TeamType, Traitor.TraitorMission> Missions = new Dictionary<Character.TeamType, Traitor.TraitorMission>();
|
||||
|
||||
public string GetCodeWords(Character.TeamType team) => Missions.TryGetValue(team, out var mission) ? mission.CodeWords : "";
|
||||
public string GetCodeResponse(Character.TeamType 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;
|
||||
|
||||
private readonly Dictionary<ulong, int> traitorCountsBySteamId = new Dictionary<ulong, int>();
|
||||
private readonly Dictionary<string, int> traitorCountsByEndPoint = new Dictionary<string, int>();
|
||||
|
||||
public int GetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint)
|
||||
{
|
||||
if (steamIdAndEndPoint.Item1 > 0 && traitorCountsBySteamId.TryGetValue(steamIdAndEndPoint.Item1, out var steamIdResult))
|
||||
{
|
||||
return steamIdResult;
|
||||
}
|
||||
return traitorCountsByEndPoint.TryGetValue(steamIdAndEndPoint.Item2, out var endPointResult) ? endPointResult : 0;
|
||||
}
|
||||
|
||||
public void SetTraitorCount(Tuple<ulong, string> steamIdAndEndPoint, int count)
|
||||
{
|
||||
if (steamIdAndEndPoint.Item1 > 0)
|
||||
{
|
||||
traitorCountsBySteamId[steamIdAndEndPoint.Item1] = count;
|
||||
}
|
||||
traitorCountsByEndPoint[steamIdAndEndPoint.Item2] = count;
|
||||
}
|
||||
|
||||
public bool IsTraitor(Character character)
|
||||
{
|
||||
if (Traitors == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return Traitors.Any(traitor => traitor.Character == character);
|
||||
}
|
||||
|
||||
public TraitorManager()
|
||||
{
|
||||
}
|
||||
|
||||
public void Start(GameServer server)
|
||||
{
|
||||
#if DISABLE_MISSIONS
|
||||
return;
|
||||
#endif
|
||||
if (server == null) return;
|
||||
|
||||
Traitor.TraitorMission.InitializeRandom();
|
||||
this.server = server;
|
||||
//TODO: configure countdowns in xml
|
||||
startCountdown = MathHelper.Lerp(90.0f, 180.0f, (float)Traitor.TraitorMission.RandomDouble());
|
||||
traitorCountsBySteamId.Clear();
|
||||
traitorCountsByEndPoint.Clear();
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
#if DISABLE_MISSIONS
|
||||
return;
|
||||
#endif
|
||||
if (Missions.Any())
|
||||
{
|
||||
bool missionCompleted = false;
|
||||
bool gameShouldEnd = false;
|
||||
Character.TeamType winningTeam = Character.TeamType.None;
|
||||
foreach (var mission in Missions)
|
||||
{
|
||||
mission.Value.Update(deltaTime, () =>
|
||||
{
|
||||
switch (mission.Key)
|
||||
{
|
||||
case Character.TeamType.Team1:
|
||||
winningTeam = (winningTeam == Character.TeamType.None) ? Character.TeamType.Team2 : Character.TeamType.None;
|
||||
break;
|
||||
case Character.TeamType.Team2:
|
||||
winningTeam = (winningTeam == Character.TeamType.None) ? Character.TeamType.Team1 : Character.TeamType.None;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
gameShouldEnd = true;
|
||||
});
|
||||
if (!gameShouldEnd && mission.Value.IsCompleted)
|
||||
{
|
||||
missionCompleted = true;
|
||||
foreach (var traitor in mission.Value.Traitors.Values)
|
||||
{
|
||||
traitor.UpdateCurrentObjective("");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (gameShouldEnd)
|
||||
{
|
||||
GameMain.GameSession.WinningTeam = winningTeam;
|
||||
GameMain.Server.EndGame();
|
||||
return;
|
||||
}
|
||||
if (missionCompleted)
|
||||
{
|
||||
Missions.Clear();
|
||||
//TODO: configure countdowns in xml
|
||||
startCountdown = MathHelper.Lerp(90.0f, 180.0f, (float)Traitor.TraitorMission.RandomDouble());
|
||||
}
|
||||
}
|
||||
else if (startCountdown > 0.0f && server.GameStarted)
|
||||
{
|
||||
startCountdown -= deltaTime;
|
||||
if (startCountdown <= 0.0f)
|
||||
{
|
||||
int playerCharactersCount = server.ConnectedClients.Sum(client => client.Character != null && !client.Character.IsDead ? 1 : 0);
|
||||
if (playerCharactersCount < server.ServerSettings.TraitorsMinPlayerCount)
|
||||
{
|
||||
startCountdown = 60.0f;
|
||||
return;
|
||||
}
|
||||
if (GameMain.GameSession.Mission is CombatMission)
|
||||
{
|
||||
var teamIds = new[] { Character.TeamType.Team1, Character.TeamType.Team2 };
|
||||
foreach (var teamId in teamIds)
|
||||
{
|
||||
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
|
||||
if (mission != null)
|
||||
{
|
||||
Missions.Add(teamId, mission);
|
||||
}
|
||||
}
|
||||
var canBeStartedCount = Missions.Sum(mission => mission.Value.CanBeStarted(server, this, mission.Key, "traitor") ? 1 : 0);
|
||||
if (canBeStartedCount >= Missions.Count)
|
||||
{
|
||||
var startSuccessCount = Missions.Sum(mission => mission.Value.Start(server, this, mission.Key, "traitor") ? 1 : 0);
|
||||
if (startSuccessCount >= Missions.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var mission = TraitorMissionPrefab.RandomPrefab()?.Instantiate();
|
||||
if (mission != null) {
|
||||
if (mission.CanBeStarted(server, this, Character.TeamType.None, "traitor"))
|
||||
{
|
||||
if (mission.Start(server, this, Character.TeamType.None, "traitor"))
|
||||
{
|
||||
Missions.Add(Character.TeamType.None, mission);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Missions.Clear();
|
||||
startCountdown = 60.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string GetEndMessage()
|
||||
{
|
||||
#if DISABLE_MISSIONS
|
||||
return "";
|
||||
#endif
|
||||
if (GameMain.Server == null || !Missions.Any()) return "";
|
||||
|
||||
return string.Join("\n\n", Missions.Select(mission => mission.Value.GlobalEndMessage));
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
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 weight = readSelectedWeight(entry);
|
||||
selected -= maxCount;
|
||||
selected += weight;
|
||||
if (selected <= 0)
|
||||
{
|
||||
writeSelectedWeight(entry, weight + selectionWeight);
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
//#define SERVER_IS_TRAITOR
|
||||
//#define ALLOW_SOLO_TRAITOR
|
||||
|
||||
using System;
|
||||
using Barotrauma.Networking;
|
||||
using Lidgren.Network;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Traitor
|
||||
{
|
||||
public class TraitorMission
|
||||
{
|
||||
private static System.Random random = null;
|
||||
|
||||
public static void InitializeRandom() => random = new System.Random((int)DateTime.UtcNow.Ticks);
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static int Random(int n) => random.Next(n);
|
||||
|
||||
// All traitor related functionality should use the following interface for generating random values
|
||||
public static double RandomDouble() => random.NextDouble();
|
||||
|
||||
private static string wordsTxt = Path.Combine("Content", "CodeWords.txt");
|
||||
|
||||
private readonly List<Objective> allObjectives = new List<Objective>();
|
||||
private readonly List<Objective> pendingObjectives = new List<Objective>();
|
||||
private readonly List<Objective> completedObjectives = new List<Objective>();
|
||||
|
||||
public virtual bool IsCompleted => pendingObjectives.Count <= 0;
|
||||
|
||||
public readonly Dictionary<string, Traitor> Traitors = new Dictionary<string, Traitor>();
|
||||
|
||||
public 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; }
|
||||
public string GlobalEndMessageSuccessDetainedTextId { get; private set; }
|
||||
public string GlobalEndMessageFailureTextId { get; private set; }
|
||||
public string GlobalEndMessageFailureDeadTextId { get; private set; }
|
||||
public string GlobalEndMessageFailureDetainedTextId { get; private set; }
|
||||
|
||||
private readonly string objectiveGoalInfoFormat = "[index]. [goalinfos]\n";
|
||||
|
||||
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)",
|
||||
(isSuccess ? completedObjectives.LastOrDefault() : pendingObjectives.FirstOrDefault())?.GoalInfos ?? ""
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public string GlobalEndMessage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Traitors.TryGetValue("traitor", out Traitor traitor))
|
||||
{
|
||||
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 "";
|
||||
}
|
||||
}
|
||||
|
||||
public Objective GetCurrentObjective(Traitor traitor)
|
||||
{
|
||||
return pendingObjectives.Count > 0 ? pendingObjectives[0] : null;
|
||||
}
|
||||
|
||||
protected List<Tuple<Client, Character>> FindTraitorCandidates(GameServer server, Character.TeamType team, params string[] traitorRoles)
|
||||
{
|
||||
var traitorCandidates = new List<Tuple<Client, Character>>();
|
||||
#if SERVER_IS_TRAITOR
|
||||
if (server.Character != null)
|
||||
{
|
||||
traitorCandidates.Add(server.Character);
|
||||
}
|
||||
else
|
||||
#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)));
|
||||
}
|
||||
return traitorCandidates;
|
||||
}
|
||||
|
||||
protected List<Character> FindCharacters()
|
||||
{
|
||||
List<Character> characters = new List<Character>();
|
||||
foreach (var character in Character.CharacterList)
|
||||
{
|
||||
characters.Add(character);
|
||||
}
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
#endif
|
||||
CodeWords = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
|
||||
CodeResponse = ToolBox.GetRandomLine(wordsTxt) + ", " + ToolBox.GetRandomLine(wordsTxt);
|
||||
Traitors.Clear();
|
||||
foreach (var role in traitorRoles)
|
||||
{
|
||||
var candidate = TraitorManager.WeightedRandom(traitorCandidates, 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);
|
||||
|
||||
var traitor = new Traitor(this, role, candidate.Item2);
|
||||
Traitors.Add(role, traitor);
|
||||
}
|
||||
|
||||
var messages = new Dictionary<Traitor, List<string>>();
|
||||
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));
|
||||
}
|
||||
|
||||
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)));
|
||||
#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);
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
public delegate void TraitorWinHandler();
|
||||
|
||||
public virtual void Update(float deltaTime, TraitorWinHandler winHandler)
|
||||
{
|
||||
if (pendingObjectives.Count <= 0 || Traitors.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var traitor in Traitors.Values)
|
||||
{
|
||||
if (traitor.Character.IsDead)
|
||||
{
|
||||
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"]))
|
||||
{
|
||||
pendingObjectives.RemoveAt(0);
|
||||
completedObjectives.Add(objective);
|
||||
if (pendingObjectives.Count > 0)
|
||||
{
|
||||
objective.EndMessage();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
++startedCount;
|
||||
}
|
||||
objective.Update(deltaTime);
|
||||
if (objective.IsCompleted)
|
||||
{
|
||||
pendingObjectives.RemoveAt(0);
|
||||
completedObjectives.Add(objective);
|
||||
if (pendingObjectives.Count > 0)
|
||||
{
|
||||
objective.EndMessage();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (!objective.CanBeCompleted)
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
else if (completedObjectives.Count >= allObjectives.Count)
|
||||
{
|
||||
foreach (var traitor in Traitors)
|
||||
{
|
||||
SteamAchievementManager.OnTraitorWin(traitor.Value.Character);
|
||||
}
|
||||
winHandler();
|
||||
}
|
||||
}
|
||||
|
||||
public delegate bool CharacterFilter(Character character);
|
||||
public Character FindKillTarget(Character traitor, CharacterFilter filter)
|
||||
{
|
||||
if (traitor == null) { return null; }
|
||||
|
||||
List<Character> validCharacters = Character.CharacterList.FindAll(c =>
|
||||
c.TeamID == traitor.TeamID &&
|
||||
c != traitor &&
|
||||
!c.IsDead &&
|
||||
(filter == null || filter(c)));
|
||||
|
||||
if (validCharacters.Count > 0)
|
||||
{
|
||||
return validCharacters[Random(validCharacters.Count)];
|
||||
}
|
||||
|
||||
#if ALLOW_SOLO_TRAITOR
|
||||
return traitor;
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
|
||||
public TraitorMission(string startText, string globalEndMessageSuccessTextId, string globalEndMessageSuccessDeadTextId, string globalEndMessageSuccessDetainedTextId, string globalEndMessageFailureTextId, string globalEndMessageFailureDeadTextId, string globalEndMessageFailureDetainedTextId, params Objective[] objectives)
|
||||
{
|
||||
StartText = startText;
|
||||
GlobalEndMessageSuccessTextId = globalEndMessageSuccessTextId;
|
||||
GlobalEndMessageSuccessDeadTextId = globalEndMessageSuccessDeadTextId;
|
||||
GlobalEndMessageSuccessDetainedTextId = globalEndMessageSuccessDetainedTextId;
|
||||
GlobalEndMessageFailureTextId = globalEndMessageFailureTextId;
|
||||
GlobalEndMessageFailureDeadTextId = globalEndMessageFailureDeadTextId;
|
||||
GlobalEndMessageFailureDetainedTextId = globalEndMessageFailureDetainedTextId;
|
||||
allObjectives.AddRange(objectives);
|
||||
pendingObjectives.AddRange(objectives);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
using System.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.Networking;
|
||||
|
||||
namespace Barotrauma {
|
||||
|
||||
class TraitorMissionPrefab
|
||||
{
|
||||
public class TraitorMissionEntry
|
||||
{
|
||||
public readonly TraitorMissionPrefab Prefab;
|
||||
public int SelectedWeight;
|
||||
|
||||
public TraitorMissionEntry(XElement element)
|
||||
{
|
||||
Prefab = new TraitorMissionPrefab(element);
|
||||
SelectedWeight = 0;
|
||||
}
|
||||
}
|
||||
public static readonly List<TraitorMissionEntry> List = new List<TraitorMissionEntry>();
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
var files = GameMain.Instance.GetFilesOfType(ContentType.TraitorMissions);
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = XMLExtensions.TryLoadXml(file);
|
||||
if (doc?.Root == null) continue;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
List.Add(new TraitorMissionEntry(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TraitorMissionPrefab RandomPrefab()
|
||||
{
|
||||
return TraitorManager.WeightedRandom(List, Traitor.TraitorMission.Random, entry => entry.SelectedWeight, (entry, weight) => entry.SelectedWeight = weight, 2, 3)?.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.Equals(character.Info.Job.Prefab.Identifier, 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());
|
||||
List<Traitor.TraitorMission.CharacterFilter> filters = 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))
|
||||
{
|
||||
filters.Add((character) => filter(attribute.Value, character));
|
||||
}
|
||||
}
|
||||
goal = new Traitor.GoalKillTarget((character) => filters.All(f => f(character)));
|
||||
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");
|
||||
goal = new Traitor.GoalFindItem(Config.GetAttributeString("identifier", null), Config.GetAttributeBool("preferNew", true), Config.GetAttributeBool("allowNew", true), Config.GetAttributeBool("allowExisting", true), Config.GetAttributeStringArray("allowedContainers", new string[] {"steelcabinet", "mediumsteelcabinet", "suppliescabinet"}));
|
||||
break;
|
||||
case "replaceinventory":
|
||||
checker.Required("containers", "replacements");
|
||||
checker.Optional("percentage");
|
||||
goal = new Traitor.GoalReplaceInventory(Config.GetAttributeStringArray("containers", new string[] { }), Config.GetAttributeStringArray("replacements", new string[] { }), Config.GetAttributeFloat("percentage", 100.0f) / 100.0f);
|
||||
break;
|
||||
case "reachdistancefromsub":
|
||||
checker.Optional("distance");
|
||||
goal = new Traitor.GoalReachDistanceFromSub(Config.GetAttributeFloat("distance", 10000.0f));
|
||||
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 class Objective
|
||||
{
|
||||
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>();
|
||||
|
||||
public Traitor.Objective Instantiate()
|
||||
{
|
||||
var result = new Traitor.Objective(InfoText, ShuffleGoalsCount, 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());
|
||||
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;
|
||||
}
|
||||
}
|
||||
/*
|
||||
public class Role
|
||||
{
|
||||
public string Job;
|
||||
}
|
||||
|
||||
public readonly Dictionary<string, Role> Roles = new Dictionary<string, Role>();
|
||||
*/
|
||||
public readonly string 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<Objective> Objectives = new List<Objective>();
|
||||
|
||||
public Traitor.TraitorMission Instantiate()
|
||||
{
|
||||
return new Traitor.TraitorMission(
|
||||
StartText ?? "TraitorMissionStartMessage",
|
||||
EndMessageSuccessText ?? "TraitorObjectiveEndMessageSuccess",
|
||||
EndMessageSuccessDeadText ?? "TraitorObjectiveEndMessageSuccessDead",
|
||||
EndMessageSuccessDetainedText ?? "TraitorObjectiveEndMessageSuccessDetained",
|
||||
EndMessageFailureText ?? "TraitorObjectiveEndMessageFailure",
|
||||
EndMessageFailureDeadText ?? "TraitorObjectiveEndMessageFailureDead",
|
||||
EndMessageFailureDetainedText ?? "TraitorObjectiveEndMessageFailureDetained",
|
||||
Objectives.ConvertAll(objective => objective.Instantiate()).ToArray());
|
||||
}
|
||||
|
||||
protected Goal LoadGoal(XElement goalRoot)
|
||||
{
|
||||
var goalType = goalRoot.GetAttributeString("type", "");
|
||||
return new Goal(goalType, goalRoot);
|
||||
}
|
||||
|
||||
protected Objective LoadObjective(XElement objectiveRoot)
|
||||
{
|
||||
var result = new Objective();
|
||||
result.ShuffleGoalsCount = objectiveRoot.GetAttributeInt("shuffleGoalsCount", -1);
|
||||
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;
|
||||
}
|
||||
|
||||
public TraitorMissionPrefab(XElement missionRoot)
|
||||
{
|
||||
Identifier = missionRoot.GetAttributeString("identifier", null);
|
||||
foreach (var element in missionRoot.Elements())
|
||||
{
|
||||
using (var checker = new AttributeChecker(element))
|
||||
{
|
||||
switch (element.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
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);
|
||||
if (objective != null)
|
||||
{
|
||||
Objectives.Add(objective);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
GameServer.Log($"Unrecognized element \"{element.Name}\"under TraitorMission.", ServerLog.MessageType.Error);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user