public readonly record struct CharacterData(
EntityUid Entity,
string Job,
- Dictionary<string, List<ConditionInfo>> Objectives,
+ Dictionary<string, List<ObjectiveInfo>> Objectives,
string? Briefing,
string EntityName
);
--- /dev/null
+using Content.Shared.Objectives.Systems;
+
+namespace Content.Client.Objectives.Systems;
+
+public sealed class ObjectivesSystem : SharedObjectivesSystem
+{
+}
foreach (var condition in conditions)
{
var conditionControl = new ObjectiveConditionsControl();
- conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.SpriteSpecifier);
+ conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.Icon);
conditionControl.ProgressTexture.Progress = condition.Progress;
var titleMessage = new FormattedMessage();
var descriptionMessage = new FormattedMessage();
using Content.Server.Roles.Jobs;
using Content.Shared.CharacterInfo;
using Content.Shared.Objectives;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Objectives.Systems;
namespace Content.Server.CharacterInfo;
[Dependency] private readonly JobSystem _jobs = default!;
[Dependency] private readonly MindSystem _minds = default!;
[Dependency] private readonly RoleSystem _roles = default!;
+ [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
public override void Initialize()
{
var entity = args.SenderSession.AttachedEntity.Value;
- var conditions = new Dictionary<string, List<ConditionInfo>>();
+ var objectives = new Dictionary<string, List<ObjectiveInfo>>();
var jobTitle = "No Profession";
string? briefing = null;
if (_minds.TryGetMind(entity, out var mindId, out var mind))
// Get objectives
foreach (var objective in mind.AllObjectives)
{
- if (!conditions.ContainsKey(objective.Prototype.Issuer))
- conditions[objective.Prototype.Issuer] = new List<ConditionInfo>();
- foreach (var condition in objective.Conditions)
- {
- conditions[objective.Prototype.Issuer].Add(new ConditionInfo(condition.Title,
- condition.Description, condition.Icon, condition.Progress));
- }
+ var info = _objectives.GetInfo(objective, mindId, mind);
+ if (info == null)
+ continue;
+
+ // group objectives by their issuer
+ var issuer = Comp<ObjectiveComponent>(objective).Issuer;
+ if (!objectives.ContainsKey(issuer))
+ objectives[issuer] = new List<ObjectiveInfo>();
+ objectives[issuer].Add(info.Value);
}
if (_jobs.MindTryGetJobName(mindId, out var jobName))
briefing = _roles.MindGetBriefing(mindId);
}
- RaiseNetworkEvent(new CharacterInfoEvent(GetNetEntity(entity), jobTitle, conditions, briefing), args.SenderSession);
+ RaiseNetworkEvent(new CharacterInfoEvent(GetNetEntity(entity), jobTitle, objectives, briefing), args.SenderSession);
}
}
using Content.Server.Ninja.Systems;
using Content.Shared.Communications;
-using Content.Shared.Objectives;
using Robust.Shared.Audio;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
public List<EntityUid> Minds = new();
/// <summary>
- /// List of objective prototype ids to add
+ /// List of objective entity prototypes to add
/// </summary>
- [DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ObjectivePrototype>))]
+ [DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
public List<string> Objectives = new();
/// <summary>
using Content.Shared.Dataset;
using Content.Shared.Mind;
using Content.Shared.Mobs.Systems;
+using Content.Shared.Objectives.Components;
using Content.Shared.PDA;
using Content.Shared.Preferences;
using Content.Shared.Roles;
if (objective == null)
continue;
- if (_mindSystem.TryAddObjective(mindId, mind, objective))
- difficulty += objective.Difficulty;
+ _mindSystem.AddObjective(mindId, mind, objective.Value);
+ difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
}
}
// assign objectives - must happen after spider charge target so that the obj requirement works
foreach (var objective in config.Objectives)
{
- if (!_mind.TryAddObjective(mindId, objective, mind))
+ if (!_mind.TryAddObjective(mindId, mind, objective))
{
Log.Error($"Failed to add {objective} to ninja {mind.OwnedEntity.Value}");
}
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Mind;
-using Content.Shared.Objectives;
+using Content.Shared.Objectives.Components;
using Robust.Server.Player;
using Robust.Shared.Console;
using Robust.Shared.Prototypes;
}
if (!IoCManager.Resolve<IPrototypeManager>()
- .TryIndex<ObjectivePrototype>(args[1], out var objectivePrototype))
+ .TryIndex<EntityPrototype>(args[1], out var proto) ||
+ !proto.TryGetComponent<ObjectiveComponent>(out _))
{
- shell.WriteLine($"Can't find matching ObjectivePrototype {objectivePrototype}");
+ shell.WriteLine($"Can't find matching objective prototype {args[1]}");
return;
}
- var mindSystem = _entityManager.System<SharedMindSystem>();
- if (!mindSystem.TryAddObjective(mindId, mind, objectivePrototype))
+ if (!minds.TryAddObjective(mindId, mind, args[1]))
{
- shell.WriteLine("Objective requirements dont allow that objective to be added.");
+ // can fail for other reasons so dont pretend to be right
+ shell.WriteLine("Failed to add the objective. Maybe requirements dont allow that objective to be added.");
}
}
}
using Content.Server.Administration;
using Content.Shared.Administration;
using Content.Shared.Mind;
+using Content.Shared.Objectives.Systems;
using Robust.Server.Player;
using Robust.Shared.Console;
}
var minds = _entities.System<SharedMindSystem>();
- if (!minds.TryGetMind(player, out _, out var mind))
+ if (!minds.TryGetMind(player, out var mindId, out var mind))
{
shell.WriteError(LocalizationManager.GetString("shell-target-entity-does-not-have-message", ("missing", "mind")));
return;
shell.WriteLine("None.");
}
+ var objectivesSystem = _entities.System<SharedObjectivesSystem>();
for (var i = 0; i < objectives.Count; i++)
{
- shell.WriteLine($"- [{i}] {objectives[i].Conditions[0].Title}");
+ var info = objectivesSystem.GetInfo(objectives[i], mindId, mind);
+ if (info == null)
+ {
+ shell.WriteLine($"- [{i}] {objectives[i]} - INVALID");
+ }
+ else
+ {
+
+ var progress = (int) (info.Value.Progress * 100f);
+ shell.WriteLine($"- [{i}] {objectives[i]} ({info.Value.Title}) ({progress}%)");
+ }
}
}
return;
}
- if (!minds.TryGetMind(session, out _, out var mind))
+ if (!minds.TryGetMind(session, out var mindId, out var mind))
{
shell.WriteLine("Can't find the mind.");
return;
if (int.TryParse(args[1], out var i))
{
var mindSystem = _entityManager.System<SharedMindSystem>();
- shell.WriteLine(mindSystem.TryRemoveObjective(mind, i)
+ shell.WriteLine(mindSystem.TryRemoveObjective(mindId, mind, i)
? "Objective successfully removed!"
: "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!");
}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the player dies to be complete.
+/// </summary>
+[RegisterComponent, Access(typeof(DieConditionSystem))]
+public sealed partial class DieConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Objective condition that requires the player to be a ninja and have doorjacked at least a random number of airlocks.
+/// Requires <see cref="NumberObjectiveComponent"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
+public sealed partial class DoorjackConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the player is on the emergency shuttle's grid when docking to CentCom.
+/// </summary>
+[RegisterComponent, Access(typeof(EscapeShuttleConditionSystem))]
+public sealed partial class EscapeShuttleConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that a target completes half of their objectives.
+/// Depends on <see cref="TargetObjectiveComponent"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(HelpProgressConditionSystem))]
+public sealed partial class HelpProgressConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that a target stays alive.
+/// Depends on <see cref="TargetObjectiveComponent"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(KeepAliveConditionSystem))]
+public sealed partial class KeepAliveConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that a target dies or, if <see cref="RequireDead"/> is false, is not on the emergency shuttle.
+/// Depends on <see cref="TargetObjectiveComponent"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
+public sealed partial class KillPersonConditionComponent : Component
+{
+ /// <summary>
+ /// Whether the target must be truly dead, ignores missing evac.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool RequireDead = false;
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that there are a certain number of other traitors alive for this objective to be given.
+/// </summary>
+[RegisterComponent, Access(typeof(MultipleTraitorsRequirementSystem))]
+public sealed partial class MultipleTraitorsRequirementComponent : Component
+{
+ /// <summary>
+ /// Number of traitors, excluding yourself, that have to exist.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int Traitors = 2;
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the player is not a member of command.
+/// </summary>
+[RegisterComponent, Access(typeof(NotCommandRequirementSystem))]
+public sealed partial class NotCommandRequirementComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+using Content.Shared.Roles;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+/// <summary>
+/// Requires that the player not have a certain job to have this objective.
+/// </summary>
+[RegisterComponent, Access(typeof(NotJobRequirementSystem))]
+public sealed partial class NotJobRequirementComponent : Component
+{
+ /// <summary>
+ /// ID of the job to ban from having this objective.
+ /// </summary>
+ [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<JobPrototype>))]
+ public string Job = string.Empty;
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Objective has a target number of something.
+/// When the objective is assigned it randomly picks this target from a minimum to a maximum.
+/// </summary>
+[RegisterComponent, Access(typeof(NumberObjectiveSystem))]
+public sealed partial class NumberObjectiveComponent : Component
+{
+ /// <summary>
+ /// Number to use in the objective condition.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public int Target;
+
+ /// <summary>
+ /// Minimum number for target to roll.
+ /// </summary>
+ [DataField(required: true)]
+ public int Min;
+
+ /// <summary>
+ /// Maximum number for target to roll.
+ /// </summary>
+ [DataField(required: true)]
+ public int Max;
+
+ /// <summary>
+ /// Optional title locale id, passed "count" with <see cref="Target"/>.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string? Title;
+
+ /// <summary>
+ /// Optional description locale id, passed "count" with <see cref="Target"/>.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public string? Description;
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+using Content.Shared.Whitelist;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the objective entity has no blacklisted components.
+/// Lets you check for incompatible objectives.
+/// </summary>
+[RegisterComponent, Access(typeof(ObjectiveBlacklistRequirementSystem))]
+public sealed partial class ObjectiveBlacklistRequirementComponent : Component
+{
+ [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ public EntityWhitelist Blacklist = new();
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Sets the target for <see cref="TargetObjectiveComponent"/> to a random head.
+/// If there are no heads it will fallback to any person.
+/// </summary>
+[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
+public sealed partial class PickRandomHeadComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Sets the target for <see cref="TargetObjectiveComponent"/> to a random person.
+/// </summary>
+[RegisterComponent, Access(typeof(KillPersonConditionSystem))]
+public sealed partial class PickRandomPersonComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Sets the target for <see cref="KeepAliveConditionComponent"/> to a random traitor.
+/// </summary>
+[RegisterComponent, Access(typeof(KeepAliveConditionSystem))]
+public sealed partial class RandomTraitorAliveComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Sets the target for <see cref="HelpProgressConditionComponent"/> to a random traitor.
+/// </summary>
+[RegisterComponent, Access(typeof(HelpProgressConditionSystem))]
+public sealed partial class RandomTraitorProgressComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+using Content.Shared.Whitelist;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the player's mind matches a whitelist.
+/// Typical use is checking for (antagonist) roles.
+/// </summary>
+[RegisterComponent, Access(typeof(RoleRequirementSystem))]
+public sealed partial class RoleRequirementComponent : Component
+{
+ [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ public EntityWhitelist Roles = new();
+}
--- /dev/null
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Marker component for social objectives and kill objectives to be mutually exclusive.
+/// </summary>
+[RegisterComponent]
+public sealed partial class SocialObjectiveComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the player is a ninja and blew up their spider charge at its target location.
+/// </summary>
+[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
+public sealed partial class SpiderChargeConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires the player to be a ninja that has a spider charge target assigned, which is almost always the case.
+/// </summary>
+[RegisterComponent, Access(typeof(SpiderChargeTargetRequirementSystem))]
+public sealed partial class SpiderChargeTargetRequirementComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that you steal a certain item.
+/// </summary>
+[RegisterComponent, Access(typeof(StealConditionSystem))]
+public sealed partial class StealConditionComponent : Component
+{
+ /// <summary>
+ /// The id of the item to steal.
+ /// </summary>
+ /// <remarks>
+ /// Works by prototype id not tags or anything so it has to be the exact item.
+ /// </remarks>
+ [DataField(required: true, customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>)), ViewVariables(VVAccess.ReadWrite)]
+ public string Prototype = string.Empty;
+
+ /// <summary>
+ /// Help newer players by saying e.g. "steal the chief engineer's advanced magboots"
+ /// instead of "steal advanced magboots. Should be a loc string.
+ /// </summary>
+ [DataField("owner"), ViewVariables(VVAccess.ReadWrite)]
+ public string? OwnerText;
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Objective condition that requires the player to be a ninja and have stolen at least a random number of technologies.
+/// Requires <see cref="NumberObjectiveComponent"/> to function.
+/// </summary>
+[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
+public sealed partial class StealResearchConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Just requires that the player is not dead, ignores evac and what not.
+/// </summary>
+[RegisterComponent, Access(typeof(SurviveConditionSystem))]
+public sealed partial class SurviveConditionComponent : Component
+{
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+[RegisterComponent, Access(typeof(TargetObjectiveSystem))]
+public sealed partial class TargetObjectiveComponent : Component
+{
+ /// <summary>
+ /// Locale id for the objective title.
+ /// It is passed "targetName" and "job" arguments.
+ /// </summary>
+ [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ public string Title = string.Empty;
+
+ /// <summary>
+ /// Mind entity id of the target.
+ /// This must be set by another system using <see cref="TargetObjectiveSystem.SetTarget"/>.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid? Target;
+}
--- /dev/null
+using Content.Server.Objectives.Systems;
+
+namespace Content.Server.Objectives.Components;
+
+/// <summary>
+/// Requires that the player is a ninja and has called in a threat.
+/// </summary>
+[RegisterComponent, Access(typeof(NinjaConditionsSystem))]
+public sealed partial class TerrorConditionComponent : Component
+{
+}
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using JetBrains.Annotations;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions
-{
- [UsedImplicitly]
- [DataDefinition]
- public sealed partial class DieCondition : IObjectiveCondition
- {
- private MindComponent? _mind;
-
- public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- return new DieCondition { _mind = mind };
- }
-
- public string Title => Loc.GetString("objective-condition-die-title");
-
- public string Description => Loc.GetString("objective-condition-die-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Mobs/Ghosts/ghost_human.rsi"), "icon");
-
- public float Progress
- {
- get
- {
- var entityManager = IoCManager.Resolve<EntityManager>();
- var mindSystem = entityManager.System<SharedMindSystem>();
- return _mind == null || mindSystem.IsCharacterDeadIc(_mind) ? 1f : 0f;
- }
- }
-
- public float Difficulty => 0.5f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is DieCondition condition && Equals(_mind, condition._mind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((DieCondition) obj);
- }
-
- public override int GetHashCode()
- {
- return (_mind != null ? _mind.GetHashCode() : 0);
- }
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions;
-
-/// <summary>
-/// Objective condition that requires the player to be a ninja and have doorjacked at least a random number of airlocks.
-/// </summary>
-[DataDefinition]
-public sealed partial class DoorjackCondition : IObjectiveCondition
-{
- private EntityUid? _mind;
- private int _target;
-
- public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
- {
- // TODO: clamp to number of doors on station incase its somehow a shittle or something
- return new DoorjackCondition {
- _mind = uid,
- _target = IoCManager.Resolve<IRobustRandom>().Next(15, 40)
- };
- }
-
- public string Title => Loc.GetString("objective-condition-doorjack-title", ("count", _target));
-
- public string Description => Loc.GetString("objective-condition-doorjack-description", ("count", _target));
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Objects/Tools/emag.rsi"), "icon");
-
- public float Progress
- {
- get
- {
- // prevent divide-by-zero
- if (_target == 0)
- return 1f;
-
- var entMan = IoCManager.Resolve<IEntityManager>();
- if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
- return 0f;
-
- if (role.DoorsJacked >= _target)
- return 1f;
-
- return (float) role.DoorsJacked / (float) _target;
- }
- }
-
- public float Difficulty => 1.5f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is DoorjackCondition cond && Equals(_mind, cond._mind) && _target == cond._target;
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- return obj is DoorjackCondition cond && cond.Equals(this);
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(_mind?.GetHashCode() ?? 0, _target);
- }
-}
+++ /dev/null
-using Content.Server.Shuttles.Systems;
-using Content.Shared.Cuffs.Components;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using JetBrains.Annotations;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions
-{
- [UsedImplicitly]
- [DataDefinition]
- public sealed partial class EscapeShuttleCondition : IObjectiveCondition
- {
- // TODO refactor all of this to be ecs
- private MindComponent? _mind;
-
- public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- return new EscapeShuttleCondition
- {
- _mind = mind,
- };
- }
-
- public string Title => Loc.GetString("objective-condition-escape-shuttle-title");
-
- public string Description => Loc.GetString("objective-condition-escape-shuttle-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Structures/Furniture/chairs.rsi"), "shuttle");
-
- public float Progress
- {
- get {
- var entMan = IoCManager.Resolve<IEntityManager>();
- var mindSystem = entMan.System<SharedMindSystem>();
-
- if (_mind?.OwnedEntity == null
- || !entMan.TryGetComponent<TransformComponent>(_mind.OwnedEntity, out var xform))
- return 0f;
-
- if (mindSystem.IsCharacterDeadIc(_mind))
- return 0f;
-
- if (entMan.TryGetComponent<CuffableComponent>(_mind.OwnedEntity, out var cuffed)
- && cuffed.CuffedHandCount > 0)
- {
- // You're not escaping if you're restrained!
- return 0f;
- }
-
- // Any emergency shuttle counts for this objective, but not pods.
- var emergencyShuttle = entMan.System<EmergencyShuttleSystem>();
- if (!emergencyShuttle.IsTargetEscaping(_mind.OwnedEntity.Value))
- return 0f;
-
- return 1f;
- }
- }
-
- public float Difficulty => 1.3f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is EscapeShuttleCondition esc && Equals(_mind, esc._mind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((EscapeShuttleCondition) obj);
- }
-
- public override int GetHashCode()
- {
- return _mind != null ? _mind.GetHashCode() : 0;
- }
- }
-}
+++ /dev/null
-using Content.Server.Shuttles.Systems;
-using Content.Shared.CCVar;
-using Content.Shared.Mind;
-using Content.Shared.Mobs.Systems;
-using Content.Shared.Objectives.Interfaces;
-using Content.Shared.Roles.Jobs;
-using Robust.Shared.Configuration;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions
-{
- public abstract class KillPersonCondition : IObjectiveCondition
- {
- // TODO refactor all of this to be ecs
- protected IEntityManager EntityManager => IoCManager.Resolve<IEntityManager>();
- protected SharedMindSystem Minds => EntityManager.System<SharedMindSystem>();
- protected SharedJobSystem Jobs => EntityManager.System<SharedJobSystem>();
- protected MobStateSystem MobStateSystem => EntityManager.System<MobStateSystem>();
- protected EntityUid? TargetMindId;
- protected MindComponent? TargetMind => EntityManager.GetComponentOrNull<MindComponent>(TargetMindId);
- public abstract IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind);
-
- /// <summary>
- /// Whether the target must be truly dead, ignores missing evac.
- /// </summary>
- protected bool RequireDead = false;
-
- public string Title
- {
- get
- {
- var mind = TargetMind;
- var targetName = mind?.CharacterName ?? "Unknown";
- var jobName = Jobs.MindTryGetJobName(TargetMindId);
-
- if (TargetMind == null)
- return Loc.GetString("objective-condition-kill-person-title", ("targetName", targetName), ("job", jobName));
-
- return Loc.GetString("objective-condition-kill-person-title", ("targetName", targetName), ("job", jobName));
- }
- }
-
- public string Description => Loc.GetString("objective-condition-kill-person-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Weapons/Guns/Pistols/viper.rsi"), "icon");
-
- public float Progress
- {
- get
- {
- if (TargetMindId == null || TargetMind?.OwnedEntity == null)
- return 1f;
-
- var entMan = IoCManager.Resolve<EntityManager>();
- var mindSystem = entMan.System<SharedMindSystem>();
- if (mindSystem.IsCharacterDeadIc(TargetMind))
- return 1f;
-
- if (RequireDead)
- return 0f;
-
- // if evac is disabled then they really do have to be dead
- var configMan = IoCManager.Resolve<IConfigurationManager>();
- if (!configMan.GetCVar(CCVars.EmergencyShuttleEnabled))
- return 0f;
-
- // target is escaping so you fail
- var emergencyShuttle = entMan.System<EmergencyShuttleSystem>();
- if (emergencyShuttle.IsTargetEscaping(TargetMind.OwnedEntity.Value))
- return 0f;
-
- // evac has left without the target, greentext since the target is afk in space with a full oxygen tank and coordinates off.
- if (emergencyShuttle.ShuttlesLeft)
- return 1f;
-
- // if evac is still here and target hasn't boarded, show 50% to give you an indicator that you are doing good
- return emergencyShuttle.EmergencyShuttleArrived ? 0.5f : 0f;
- }
- }
-
- public float Difficulty => 1.75f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is KillPersonCondition kpc && Equals(TargetMindId, kpc.TargetMindId);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((KillPersonCondition) obj);
- }
-
- public override int GetHashCode()
- {
- return TargetMindId?.GetHashCode() ?? 0;
- }
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Shared.Mind;
-using Content.Shared.Mind.Components;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Random;
-
-namespace Content.Server.Objectives.Conditions;
-
-[DataDefinition]
-public sealed partial class KillRandomHeadCondition : KillPersonCondition
-{
- // TODO refactor all of this to be ecs
- public override IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- RequireDead = true;
-
- var allHumans = EntityManager.EntityQuery<MindContainerComponent>(true).Where(mc =>
- {
- var entity = EntityManagerExt.GetComponentOrNull<MindComponent>(EntityManager, (EntityUid?) mc.Mind)?.OwnedEntity;
-
- if (entity == default)
- return false;
-
- return EntityManager.TryGetComponent(entity, out MobStateComponent? mobState) &&
- MobStateSystem.IsAlive(entity.Value, mobState) &&
- mc.Mind != mindId;
- }).Select(mc => mc.Mind).ToList();
-
- if (allHumans.Count == 0)
- return new DieCondition(); // I guess I'll die
-
- var allHeads = allHumans
- .Where(mind => Jobs.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify)
- .ToList();
-
- if (allHeads.Count == 0)
- allHeads = allHumans; // fallback to non-head target
-
- return new KillRandomHeadCondition { TargetMindId = IoCManager.Resolve<IRobustRandom>().Pick(allHeads) };
- }
-
- public string Description => Loc.GetString("objective-condition-kill-head-description");
-}
+++ /dev/null
-using Content.Shared.Humanoid;
-using Content.Shared.Mind;
-using Content.Shared.Mind.Components;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Random;
-
-namespace Content.Server.Objectives.Conditions;
-
-[DataDefinition]
-public sealed partial class KillRandomPersonCondition : KillPersonCondition
-{
- public override IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- var allHumans = new List<EntityUid>();
- var query = EntityManager.EntityQuery<MindContainerComponent, HumanoidAppearanceComponent>(true);
- foreach (var (mc, _) in query)
- {
- var entity = EntityManager.GetComponentOrNull<MindComponent>(mc.Mind)?.OwnedEntity;
- if (entity == default)
- continue;
-
- if (EntityManager.TryGetComponent(entity, out MobStateComponent? mobState) &&
- MobStateSystem.IsAlive(entity.Value, mobState) &&
- mc.Mind != mindId && mc.Mind != null)
- {
- allHumans.Add(mc.Mind.Value);
- }
- }
-
- if (allHumans.Count == 0)
- return new DieCondition(); // I guess I'll die
-
- return new KillRandomPersonCondition {TargetMindId = IoCManager.Resolve<IRobustRandom>().Pick(allHumans)};
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.GameTicking.Rules;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Content.Shared.Roles.Jobs;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions
-{
- [DataDefinition]
- public sealed partial class RandomTraitorAliveCondition : IObjectiveCondition
- {
- private EntityUid? _targetMind;
-
- public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- var entityMgr = IoCManager.Resolve<IEntityManager>();
-
- var traitors = Enumerable.ToList<(EntityUid Id, MindComponent Mind)>(entityMgr.System<TraitorRuleSystem>().GetOtherTraitorMindsAliveAndConnected(mind));
-
- if (traitors.Count == 0)
- return new EscapeShuttleCondition(); //You were made a traitor by admins, and are the first/only.
- return new RandomTraitorAliveCondition { _targetMind = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Id };
- }
-
- public string Title
- {
- get
- {
- var targetName = string.Empty;
- var ents = IoCManager.Resolve<IEntityManager>();
- var jobs = ents.System<SharedJobSystem>();
- var jobName = jobs.MindTryGetJobName(_targetMind);
-
- if (_targetMind == null)
- return Loc.GetString("objective-condition-other-traitor-alive-title", ("targetName", targetName), ("job", jobName));
-
- if (ents.TryGetComponent(_targetMind, out MindComponent? mind) &&
- mind.OwnedEntity is {Valid: true} owned)
- {
- targetName = ents.GetComponent<MetaDataComponent>(owned).EntityName;
- }
-
- return Loc.GetString("objective-condition-other-traitor-alive-title", ("targetName", targetName), ("job", jobName));
- }
- }
-
- public string Description => Loc.GetString("objective-condition-other-traitor-alive-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Misc/bureaucracy.rsi"), "folder-white");
-
- public float Progress
- {
- get
- {
- var entityManager = IoCManager.Resolve<EntityManager>();
- var mindSystem = entityManager.System<SharedMindSystem>();
- return !entityManager.TryGetComponent(_targetMind, out MindComponent? mind) ||
- !mindSystem.IsCharacterDeadIc(mind)
- ? 1f
- : 0f;
- }
- }
-
- public float Difficulty => 1.75f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is RandomTraitorAliveCondition kpc && Equals(_targetMind, kpc._targetMind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- return obj is RandomTraitorAliveCondition alive && alive.Equals(this);
- }
-
- public override int GetHashCode()
- {
- return _targetMind?.GetHashCode() ?? 0;
- }
- }
-}
+++ /dev/null
-using System.Linq;
-using Content.Server.GameTicking.Rules;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Content.Shared.Roles.Jobs;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions
-{
- [DataDefinition]
- public sealed partial class RandomTraitorProgressCondition : IObjectiveCondition
- {
- // TODO ecs all of this
- private EntityUid? _targetMind;
-
- public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- //todo shit of a fuck
- var entityMgr = IoCManager.Resolve<IEntityManager>();
-
- var traitors = entityMgr.System<TraitorRuleSystem>().GetOtherTraitorMindsAliveAndConnected(mind).ToList();
- List<EntityUid> removeList = new();
-
- foreach (var traitor in traitors)
- {
- foreach (var objective in traitor.Mind.AllObjectives)
- {
- foreach (var condition in objective.Conditions)
- {
- if (condition is RandomTraitorProgressCondition)
- {
- removeList.Add(traitor.Id);
- }
- }
- }
- }
-
- foreach (var traitor in removeList)
- {
- traitors.RemoveAll(t => t.Id == traitor);
- }
-
- if (traitors.Count == 0) return new EscapeShuttleCondition{}; //You were made a traitor by admins, and are the first/only.
- return new RandomTraitorProgressCondition { _targetMind = IoCManager.Resolve<IRobustRandom>().Pick(traitors).Id };
- }
-
- public string Title
- {
- get
- {
- var targetName = string.Empty;
- var entities = IoCManager.Resolve<IEntityManager>();
- var jobs = entities.System<SharedJobSystem>();
- var jobName = jobs.MindTryGetJobName(_targetMind);
-
- if (_targetMind == null)
- return Loc.GetString("objective-condition-other-traitor-progress-title", ("targetName", targetName), ("job", jobName));
-
- if (entities.TryGetComponent(_targetMind, out MindComponent? mind) &&
- mind.OwnedEntity is {Valid: true} owned)
- {
- targetName = entities.GetComponent<MetaDataComponent>(owned).EntityName;
- }
-
- return Loc.GetString("objective-condition-other-traitor-progress-title", ("targetName", targetName), ("job", jobName));
- }
- }
-
- public string Description => Loc.GetString("objective-condition-other-traitor-progress-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ("Objects/Misc/bureaucracy.rsi"), "folder-white");
-
- public float Progress
- {
- get
- {
- float total = 0f; // how much progress they have
- float max = 0f; // how much progress is needed for 100%
-
- if (_targetMind == null)
- {
- Logger.Error("Null target on RandomTraitorProgressCondition.");
- return 1f;
- }
-
- var entities = IoCManager.Resolve<IEntityManager>();
- if (entities.TryGetComponent(_targetMind, out MindComponent? mind))
- {
- foreach (var objective in mind.AllObjectives)
- {
- foreach (var condition in objective.Conditions)
- {
- max++; // things can only be up to 100% complete yeah
- total += condition.Progress;
- }
- }
- }
-
- if (max == 0f)
- {
- Logger.Error("RandomTraitorProgressCondition assigned someone with no objectives to be helped.");
- return 1f;
- }
-
- var completion = total / max;
-
- if (completion >= 0.5f)
- return 1f;
- else
- return completion / 0.5f;
- }
- }
-
- public float Difficulty => 2.5f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is RandomTraitorProgressCondition kpc && Equals(_targetMind, kpc._targetMind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- return obj is RandomTraitorProgressCondition alive && alive.Equals(this);
- }
-
- public override int GetHashCode()
- {
- return _targetMind?.GetHashCode() ?? 0;
- }
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Server.Warps;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions;
-
-/// <summary>
-/// Objective condition that requires the player to be a ninja and have detonated their spider charge.
-/// </summary>
-[DataDefinition]
-public sealed partial class SpiderChargeCondition : IObjectiveCondition
-{
- private EntityUid? _mind;
-
- public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
- {
- return new SpiderChargeCondition {
- _mind = uid
- };
- }
-
- public string Title
- {
- get
- {
- var entMan = IoCManager.Resolve<IEntityManager>();
- if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role)
- || role.SpiderChargeTarget == null
- || !entMan.TryGetComponent<WarpPointComponent>(role.SpiderChargeTarget, out var warp)
- || warp.Location == null)
- // this should never really happen but eh
- return Loc.GetString("objective-condition-spider-charge-no-target");
-
- return Loc.GetString("objective-condition-spider-charge-title", ("location", warp.Location));
- }
- }
-
- public string Description => Loc.GetString("objective-condition-spider-charge-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Objects/Weapons/Bombs/spidercharge.rsi"), "icon");
-
- public float Progress
- {
- get
- {
- var entMan = IoCManager.Resolve<EntityManager>();
- if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
- return 0f;
-
- return role.SpiderChargeDetonated ? 1f : 0f;
- }
- }
-
- public float Difficulty => 2.5f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is SpiderChargeCondition cond && Equals(_mind, cond._mind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- return obj is SpiderChargeCondition cond && cond.Equals(this);
- }
-
- public override int GetHashCode()
- {
- return _mind?.GetHashCode() ?? 0;
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using JetBrains.Annotations;
-using Robust.Shared.Containers;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Serialization;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions
-{
- // Oh god my eyes
- [UsedImplicitly]
- [DataDefinition]
- public sealed partial class StealCondition : IObjectiveCondition, ISerializationHooks
- {
- private EntityUid? _mind;
- [DataField("prototype")] private string _prototypeId = string.Empty;
-
- /// <summary>
- /// Help newer players by saying e.g. "steal the chief engineer's advanced magboots"
- /// instead of "steal advanced magboots. Should be a loc string.
- /// </summary>
- [DataField("owner")] private string? _owner = null;
-
- public IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind)
- {
- return new StealCondition
- {
- _mind = mindId,
- _prototypeId = _prototypeId,
- _owner = _owner
- };
- }
-
- private string PrototypeName =>
- IoCManager.Resolve<IPrototypeManager>().TryIndex<EntityPrototype>(_prototypeId, out var prototype)
- ? prototype.Name
- : "[CANNOT FIND NAME]";
-
- public string Title =>
- _owner == null
- ? Loc.GetString("objective-condition-steal-title-no-owner", ("itemName", Loc.GetString(PrototypeName)))
- : Loc.GetString("objective-condition-steal-title", ("owner", Loc.GetString(_owner)), ("itemName", Loc.GetString(PrototypeName)));
-
- public string Description => Loc.GetString("objective-condition-steal-description",("itemName", Loc.GetString(PrototypeName)));
-
- public SpriteSpecifier Icon => new SpriteSpecifier.EntityPrototype(_prototypeId);
-
- public float Progress
- {
- get
- {
- var entMan = IoCManager.Resolve<IEntityManager>();
-
- // TODO make this a container system function
- // or: just iterate through transform children, instead of containers?
-
- var metaQuery = entMan.GetEntityQuery<MetaDataComponent>();
- var managerQuery = entMan.GetEntityQuery<ContainerManagerComponent>();
- var stack = new Stack<ContainerManagerComponent>();
-
- if (!entMan.TryGetComponent(_mind, out MindComponent? mind))
- return 0;
-
- if (!metaQuery.TryGetComponent(mind.OwnedEntity, out var meta))
- return 0;
-
- if (meta.EntityPrototype?.ID == _prototypeId)
- return 1;
-
- if (!managerQuery.TryGetComponent(mind.OwnedEntity, out var currentManager))
- return 0;
-
- do
- {
- foreach (var container in currentManager.Containers.Values)
- {
- foreach (var entity in container.ContainedEntities)
- {
- if (metaQuery.GetComponent(entity).EntityPrototype?.ID == _prototypeId)
- return 1;
- if (!managerQuery.TryGetComponent(entity, out var containerManager))
- continue;
- stack.Push(containerManager);
- }
- }
- } while (stack.TryPop(out currentManager));
-
- return 0;
- }
- }
-
- public float Difficulty => 2.25f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is StealCondition stealCondition &&
- Equals(_mind, stealCondition._mind) &&
- _prototypeId == stealCondition._prototypeId;
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((StealCondition) obj);
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(_mind, _prototypeId);
- }
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Random;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions;
-
-/// <summary>
-/// Objective condition that requires the player to be a ninja and have stolen at least a random number of technologies.
-/// </summary>
-[DataDefinition]
-public sealed partial class StealResearchCondition : IObjectiveCondition
-{
- private EntityUid? _mind;
- private int _target;
-
- public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
- {
- // TODO: clamp to number of research nodes in a single discipline maybe so easily maintainable
- return new StealResearchCondition {
- _mind = uid,
- _target = IoCManager.Resolve<IRobustRandom>().Next(5, 10)
- };
- }
-
- public string Title => Loc.GetString("objective-condition-steal-research-title", ("count", _target));
-
- public string Description => Loc.GetString("objective-condition-steal-research-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Structures/Machines/server.rsi"), "server");
-
- public float Progress
- {
- get
- {
- // prevent divide-by-zero
- if (_target == 0)
- return 1f;
-
- var entMan = IoCManager.Resolve<IEntityManager>();
- if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
- return 0f;
-
- if (role.DownloadedNodes.Count >= _target)
- return 1f;
-
- return (float) role.DownloadedNodes.Count / (float) _target;
- }
- }
-
- public float Difficulty => 2.5f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is StealResearchCondition cond && Equals(_mind, cond._mind) && _target == cond._target;
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- return obj is StealResearchCondition cond && cond.Equals(this);
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(_mind?.GetHashCode() ?? 0, _target);
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions;
-
-/// <summary>
-/// Just requires that the player is not dead, ignores evac and what not.
-/// </summary>
-[DataDefinition]
-public sealed partial class SurviveCondition : IObjectiveCondition
-{
- private EntityUid? _mind;
-
- public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
- {
- return new SurviveCondition {_mind = uid};
- }
-
- public string Title => Loc.GetString("objective-condition-survive-title");
-
- public string Description => Loc.GetString("objective-condition-survive-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Clothing/Mask/ninja.rsi"), "icon");
-
- public float Difficulty => 0.5f;
-
- public float Progress
- {
- get
- {
- var entMan = IoCManager.Resolve<IEntityManager>();
- if (!entMan.TryGetComponent<MindComponent>(_mind, out var mind))
- return 0f;
-
- var mindSystem = entMan.System<SharedMindSystem>();
- return mindSystem.IsCharacterDeadIc(mind) ? 0f : 1f;
- }
- }
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is SurviveCondition condition && Equals(_mind, condition._mind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((SurviveCondition) obj);
- }
-
- public override int GetHashCode()
- {
- return (_mind != null ? _mind.GetHashCode() : 0);
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Utility;
-
-namespace Content.Server.Objectives.Conditions;
-
-/// <summary>
-/// Objective condition that requires the player to be a ninja and have called in a threat.
-/// </summary>
-[DataDefinition]
-public sealed partial class TerrorCondition : IObjectiveCondition
-{
- private EntityUid? _mind;
-
- public IObjectiveCondition GetAssigned(EntityUid uid, MindComponent mind)
- {
- return new TerrorCondition {_mind = uid};
- }
-
- public string Title => Loc.GetString("objective-condition-terror-title");
-
- public string Description => Loc.GetString("objective-condition-terror-description");
-
- public SpriteSpecifier Icon => new SpriteSpecifier.Rsi(new ResPath("Objects/Fun/Instruments/otherinstruments.rsi"), "red_phone");
-
- public float Progress
- {
- get
- {
- var entMan = IoCManager.Resolve<EntityManager>();
- if (!entMan.TryGetComponent<NinjaRoleComponent>(_mind, out var role))
- return 0f;
-
- return role.CalledInThreat ? 1f : 0f;
- }
- }
-
- public float Difficulty => 2.75f;
-
- public bool Equals(IObjectiveCondition? other)
- {
- return other is TerrorCondition cond && Equals(_mind, cond._mind);
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- return obj is TerrorCondition cond && cond.Equals(this);
- }
-
- public override int GetHashCode()
- {
- return _mind?.GetHashCode() ?? 0;
- }
-}
using Content.Server.GameTicking.Rules.Components;
using Content.Server.Mind;
using Content.Shared.Mind;
-using Content.Shared.Objectives;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Objectives.Systems;
using Content.Shared.Random;
using Content.Shared.Random.Helpers;
using Robust.Shared.Prototypes;
namespace Content.Server.Objectives;
-public sealed class ObjectivesSystem : EntitySystem
+public sealed class ObjectivesSystem : SharedObjectivesSystem
{
[Dependency] private readonly GameTicker _gameTicker = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
/// </summary>
private void OnRoundEndText(RoundEndTextAppendEvent ev)
{
+ // go through each gamerule getting data for the roundend summary.
+ var summaries = new Dictionary<string, Dictionary<string, List<EntityUid>>>();
var query = EntityQueryEnumerator<GameRuleComponent>();
while (query.MoveNext(out var uid, out var gameRule))
{
if (info.Minds.Count == 0)
continue;
+ // first group the gamerules by their agents, for example 2 different dragons
var agent = info.AgentName;
- var result = Loc.GetString("objectives-round-end-result", ("count", info.Minds.Count), ("agent", agent));
- var prepend = new ObjectivesTextPrependEvent(result);
+ if (!summaries.ContainsKey(agent))
+ summaries[agent] = new Dictionary<string, List<EntityUid>>();
+
+ var prepend = new ObjectivesTextPrependEvent("");
RaiseLocalEvent(uid, ref prepend);
- // space between the start text and player list
- result = prepend.Text + "\n";
- foreach (var mindId in info.Minds)
+ // next group them by their prepended texts
+ // for example with traitor rule, group them by the codewords they share
+ var summary = summaries[agent];
+ if (summary.ContainsKey(prepend.Text))
{
- if (!TryComp(mindId, out MindComponent? mind))
- continue;
-
- var name = mind.CharacterName;
- _mind.TryGetSession(mindId, out var session);
- var username = session?.Name;
+ // same prepended text (usually empty) so combine them
+ summary[prepend.Text].AddRange(info.Minds);
+ }
+ else
+ {
+ summary[prepend.Text] = info.Minds;
+ }
+ }
- string title;
- if (username != null)
- {
- if (name != null)
- title = Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
- else
- title = Loc.GetString("objectives-player-user", ("user", username));
- }
- else
- {
- // nothing to identify the player by, just give up
- if (name == null)
- continue;
+ // convert the data into summary text
+ foreach (var (agent, summary) in summaries)
+ {
+ // first get the total number of players that were in these game rules combined
+ var total = 0;
+ foreach (var (_, minds) in summary)
+ {
+ total += minds.Count;
+ }
- title = Loc.GetString("objectives-player-named", ("name", name));
- }
+ var result = Loc.GetString("objectives-round-end-result", ("count", total), ("agent", agent));
+ // next add all the players with its own prepended text
+ foreach (var (prepend, minds) in summary)
+ {
+ if (prepend != string.Empty)
+ result += prepend;
+ // add space between the start text and player list
result += "\n";
- var objectives = mind.AllObjectives.ToArray();
- if (objectives.Length == 0)
- {
- result += Loc.GetString("objectives-no-objectives", ("title", title), ("agent", agent));
+ AddSummary(ref result, agent, minds);
+ }
+
+ ev.AddLine(result + "\n");
+ }
+ }
+
+ private void AddSummary(ref string result, string agent, List<EntityUid> minds)
+ {
+ foreach (var mindId in minds)
+ {
+ if (!TryComp(mindId, out MindComponent? mind))
+ continue;
+
+ var name = mind.CharacterName;
+ _mind.TryGetSession(mindId, out var session);
+ var username = session?.Name;
+
+ string title;
+ if (username != null)
+ {
+ if (name != null)
+ title = Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
+ else
+ title = Loc.GetString("objectives-player-user", ("user", username));
+ }
+ else
+ {
+ // nothing to identify the player by, just give up
+ if (name == null)
continue;
- }
- result += Loc.GetString("objectives-with-objectives", ("title", title), ("agent", agent));
+ title = Loc.GetString("objectives-player-named", ("name", name));
+ }
- foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer))
+ result += "\n";
+
+ var objectives = mind.AllObjectives.ToArray();
+ if (objectives.Length == 0)
+ {
+ result += Loc.GetString("objectives-no-objectives", ("title", title), ("agent", agent));
+ continue;
+ }
+
+ result += Loc.GetString("objectives-with-objectives", ("title", title), ("agent", agent));
+
+ foreach (var objectiveGroup in objectives.GroupBy(o => Comp<ObjectiveComponent>(o).Issuer))
+ {
+ result += "\n" + Loc.GetString($"objective-issuer-{objectiveGroup.Key}");
+
+ foreach (var objective in objectiveGroup)
{
- result += "\n" + Loc.GetString($"objective-issuer-{objectiveGroup.Key}");
+ var info = GetInfo(objective, mindId, mind);
+ if (info == null)
+ continue;
- foreach (var objective in objectiveGroup)
+ var objectiveTitle = info.Value.Title;
+ var progress = info.Value.Progress;
+ if (progress > 0.99f)
{
- foreach (var condition in objective.Conditions)
- {
- var progress = condition.Progress;
- if (progress > 0.99f)
- {
- result += "\n- " + Loc.GetString(
- "objectives-condition-success",
- ("condition", condition.Title),
- ("markupColor", "green")
- );
- }
- else
- {
- result += "\n- " + Loc.GetString(
- "objectives-condition-fail",
- ("condition", condition.Title),
- ("progress", (int) (progress * 100)),
- ("markupColor", "red")
- );
- }
- }
+ result += "\n- " + Loc.GetString(
+ "objectives-objective-success",
+ ("objective", objectiveTitle),
+ ("markupColor", "green")
+ );
+ }
+ else
+ {
+ result += "\n- " + Loc.GetString(
+ "objectives-objective-fail",
+ ("objective", objectiveTitle),
+ ("progress", (int) (progress * 100)),
+ ("markupColor", "red")
+ );
}
}
}
-
- ev.AddLine(result + "\n");
}
}
- public ObjectivePrototype? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto)
+ public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto)
{
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(objectiveGroupProto, out var groups))
{
- Log.Error("Tried to get a random objective, but can't index WeightedRandomPrototype " + objectiveGroupProto);
+ Log.Error($"Tried to get a random objective, but can't index WeightedRandomPrototype {objectiveGroupProto}");
return null;
}
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(groupName, out var group))
{
- Log.Error("Couldn't index objective group prototype" + groupName);
+ Log.Error($"Couldn't index objective group prototype {groupName}");
return null;
}
- if (_prototypeManager.TryIndex<ObjectivePrototype>(group.Pick(_random), out var objective)
- && objective.CanBeAssigned(mindId, mind))
+ var proto = group.Pick(_random);
+ var objective = TryCreateObjective(mindId, mind, proto);
+ if (objective != null)
return objective;
- else
- tries++;
+
+ tries++;
}
return null;
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-
-namespace Content.Server.Objectives.Requirements
-{
- [DataDefinition]
- public sealed partial class IncompatibleConditionsRequirement : IObjectiveRequirement
- {
- [DataField("conditions")]
- private List<string> _incompatibleConditions = new();
-
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- foreach (var objective in mind.AllObjectives)
- {
- foreach (var condition in objective.Conditions)
- {
- foreach (var incompatibleCondition in _incompatibleConditions)
- {
- if (incompatibleCondition == condition.GetType().Name) return false;
- }
- }
- }
-
- return true;
- }
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-
-namespace Content.Server.Objectives.Requirements
-{
- [DataDefinition]
- public sealed partial class IncompatibleObjectivesRequirement : IObjectiveRequirement
- {
- [DataField("objectives")]
- private List<string> _incompatibleObjectives = new();
-
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- foreach (var objective in mind.AllObjectives)
- {
- foreach (var incompatibleObjective in _incompatibleObjectives)
- {
- if (incompatibleObjective == objective.Prototype.ID) return false;
- }
- }
-
- return true;
- }
- }
-}
+++ /dev/null
-using Content.Server.GameTicking.Rules;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-
-namespace Content.Server.Objectives.Requirements
-{
- [DataDefinition]
- public sealed partial class MultipleTraitorsRequirement : IObjectiveRequirement
- {
- [DataField("traitors")]
- private int _requiredTraitors = 2;
-
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- return EntitySystem.Get<TraitorRuleSystem>().GetOtherTraitorMindsAliveAndConnected(mind).Count >= _requiredTraitors;
- }
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-
-namespace Content.Server.Objectives.Requirements;
-
-/// <summary>
-/// Requires the player's mind to have the ninja role component, aka be a ninja.
-/// </summary>
-[DataDefinition]
-public sealed partial class NinjaRequirement : IObjectiveRequirement
-{
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- var entMan = IoCManager.Resolve<IEntityManager>();
- return entMan.HasComponent<NinjaRoleComponent>(mindId);
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Content.Shared.Roles;
-using Content.Shared.Roles.Jobs;
-using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
-
-namespace Content.Server.Objectives.Requirements
-{
- [DataDefinition]
- public sealed partial class NotRoleRequirement : IObjectiveRequirement
- {
- [DataField("roleId", customTypeSerializer:typeof(PrototypeIdSerializer<JobPrototype>), required:true)]
- private string _roleId = default!;
-
- /// <summary>
- /// This requirement is met if the traitor is NOT the roleId, and fails if they are.
- /// </summary>
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- // TODO ECS this shit i keep seeing shitcode everywhere
- var entities = IoCManager.Resolve<IEntityManager>();
- if (!entities.TryGetComponent(mindId, out JobComponent? job))
- return true;
-
- return job.PrototypeId != _roleId;
- }
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-
-namespace Content.Server.Objectives.Requirements;
-
-/// <summary>
-/// Requires the player to be a ninja that has a spider charge target assigned, which is almost always the case.
-/// </summary>
-[DataDefinition]
-public sealed partial class SpiderChargeTargetRequirement : IObjectiveRequirement
-{
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- var entMan = IoCManager.Resolve<IEntityManager>();
- entMan.TryGetComponent<NinjaRoleComponent>(mindId, out var role);
- return role?.SpiderChargeTarget != null;
- }
-}
+++ /dev/null
-using Content.Server.Roles;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Content.Shared.Roles;
-using JetBrains.Annotations;
-
-namespace Content.Server.Objectives.Requirements
-{
- [UsedImplicitly]
- [DataDefinition]
- public sealed partial class TraitorRequirement : IObjectiveRequirement
- {
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- var roleSystem = IoCManager.Resolve<IEntityManager>().System<SharedRoleSystem>();
- return roleSystem.MindHasRole<TraitorRoleComponent>(mindId);
- }
- }
-}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+public sealed class DieConditionSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<DieConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+ }
+
+ private void OnGetProgress(EntityUid uid, DieConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = _mind.IsCharacterDeadIc(args.Mind) ? 1f : 0f;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Server.Shuttles.Systems;
+using Content.Shared.Cuffs.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+public sealed class EscapeShuttleConditionSystem : EntitySystem
+{
+ [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<EscapeShuttleConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+ }
+
+ private void OnGetProgress(EntityUid uid, EscapeShuttleConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = GetProgress(args.MindId, args.Mind);
+ }
+
+ private float GetProgress(EntityUid mindId, MindComponent mind)
+ {
+ // not escaping alive if you're deleted/dead
+ if (mind.OwnedEntity == null || _mind.IsCharacterDeadIc(mind))
+ return 0f;
+
+ // You're not escaping if you're restrained!
+ if (TryComp<CuffableComponent>(mind.OwnedEntity, out var cuffed) && cuffed.CuffedHandCount > 0)
+ return 0f;
+
+ // Any emergency shuttle counts for this objective, but not pods.
+ return _emergencyShuttle.IsTargetEscaping(mind.OwnedEntity.Value) ? 1f : 0f;
+ }
+}
--- /dev/null
+using Content.Server.GameTicking.Rules;
+using Content.Server.Objectives.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Objectives.Systems;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.Random;
+using System.Linq;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles help progress condition logic and picking random help targets.
+/// </summary>
+public sealed class HelpProgressConditionSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
+ [Dependency] private readonly TargetObjectiveSystem _target = default!;
+ [Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<HelpProgressConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+
+ SubscribeLocalEvent<RandomTraitorProgressComponent, ObjectiveAssignedEvent>(OnTraitorAssigned);
+ }
+
+ private void OnGetProgress(EntityUid uid, HelpProgressConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ if (!_target.GetTarget(uid, out var target))
+ return;
+
+ args.Progress = GetProgress(target.Value);
+ }
+
+ private void OnTraitorAssigned(EntityUid uid, RandomTraitorProgressComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ // invalid prototype
+ if (!TryComp<TargetObjectiveComponent>(uid, out var target))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ var traitors = _traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind)
+ .Select(pair => pair.Item1)
+ .ToHashSet();
+ var removeList = new List<EntityUid>();
+
+ // cant help anyone who is tasked with helping:
+ // 1. thats boring
+ // 2. no cyclic progress dependencies!!!
+ foreach (var traitor in traitors)
+ {
+ // TODO: replace this with TryComp<ObjectivesComponent>(traitor) or something when objectives are moved out of mind
+ if (!TryComp<MindComponent>(traitor, out var mind))
+ continue;
+
+ foreach (var objective in mind.AllObjectives)
+ {
+ if (HasComp<HelpProgressConditionComponent>(objective))
+ removeList.Add(traitor);
+ }
+ }
+
+ foreach (var tot in removeList)
+ {
+ traitors.Remove(tot);
+ }
+
+ // no more helpable traitors
+ if (traitors.Count == 0)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ _target.SetTarget(uid, _random.Pick(traitors), target);
+ }
+
+ private float GetProgress(EntityUid target)
+ {
+ var total = 0f; // how much progress they have
+ var max = 0f; // how much progress is needed for 100%
+
+ if (TryComp<MindComponent>(target, out var mind))
+ {
+ foreach (var objective in mind.AllObjectives)
+ {
+ // this has the potential to loop forever, anything setting target has to check that there is no HelpProgressCondition.
+ var info = _objectives.GetInfo(objective, target, mind);
+ if (info == null)
+ continue;
+
+ max++; // things can only be up to 100% complete yeah
+ total += info.Value.Progress;
+ }
+ }
+
+ // no objectives that can be helped with...
+ if (max == 0f)
+ return 1f;
+
+ // require 50% completion for this one to be complete
+ var completion = total / max;
+ return completion >= 0.5f ? 1f : completion / 0.5f;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Server.GameTicking.Rules;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.Random;
+using System.Linq;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles keep alive condition logic and picking random traitors to keep alive.
+/// </summary>
+public sealed class KeepAliveConditionSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+ [Dependency] private readonly TargetObjectiveSystem _target = default!;
+ [Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<KeepAliveConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+
+ SubscribeLocalEvent<RandomTraitorAliveComponent, ObjectiveAssignedEvent>(OnAssigned);
+ }
+
+ private void OnGetProgress(EntityUid uid, KeepAliveConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ if (!_target.GetTarget(uid, out var target))
+ return;
+
+ args.Progress = GetProgress(target.Value);
+ }
+
+ private void OnAssigned(EntityUid uid, RandomTraitorAliveComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ // invalid prototype
+ if (!TryComp<TargetObjectiveComponent>(uid, out var target))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ var traitors = Enumerable.ToList<(EntityUid Id, MindComponent Mind)>(_traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind));
+
+ // You are the first/only traitor.
+ if (traitors.Count == 0)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ _target.SetTarget(uid, _random.Pick(traitors).Id, target);
+ }
+
+ private float GetProgress(EntityUid target)
+ {
+ if (!TryComp<MindComponent>(target, out var mind))
+ return 0f;
+
+ return _mind.IsCharacterDeadIc(mind) ? 0f : 1f;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Server.Shuttles.Systems;
+using Content.Shared.CCVar;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.Configuration;
+using Robust.Shared.Random;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles kill person condition logic and picking random kill targets.
+/// </summary>
+public sealed class KillPersonConditionSystem : EntitySystem
+{
+ [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!;
+ [Dependency] private readonly IConfigurationManager _config = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedJobSystem _job = default!;
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+ [Dependency] private readonly TargetObjectiveSystem _target = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<KillPersonConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+
+ SubscribeLocalEvent<PickRandomPersonComponent, ObjectiveAssignedEvent>(OnPersonAssigned);
+
+ SubscribeLocalEvent<PickRandomHeadComponent, ObjectiveAssignedEvent>(OnHeadAssigned);
+ }
+
+ private void OnGetProgress(EntityUid uid, KillPersonConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ if (!_target.GetTarget(uid, out var target))
+ return;
+
+ args.Progress = GetProgress(target.Value, comp.RequireDead);
+ }
+
+ private void OnPersonAssigned(EntityUid uid, PickRandomPersonComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ // invalid objective prototype
+ if (!TryComp<TargetObjectiveComponent>(uid, out var target))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ // target already assigned
+ if (target.Target != null)
+ return;
+
+ // no other humans to kill
+ var allHumans = _mind.GetAliveHumansExcept(args.MindId);
+ if (allHumans.Count == 0)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ _target.SetTarget(uid, _random.Pick(allHumans), target);
+ }
+
+ private void OnHeadAssigned(EntityUid uid, PickRandomHeadComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ // invalid prototype
+ if (!TryComp<TargetObjectiveComponent>(uid, out var target))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ // target already assigned
+ if (target.Target != null)
+ return;
+
+ // no other humans to kill
+ var allHumans = _mind.GetAliveHumansExcept(args.MindId);
+ if (allHumans.Count == 0)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ var allHeads = new List<EntityUid>();
+ foreach (var mind in allHumans)
+ {
+ // RequireAdminNotify used as a cheap way to check for command department
+ if (_job.MindTryGetJob(mind, out _, out var prototype) && prototype.RequireAdminNotify)
+ allHeads.Add(mind);
+ }
+
+ if (allHeads.Count == 0)
+ allHeads = allHumans; // fallback to non-head target
+
+ _target.SetTarget(uid, _random.Pick(allHeads), target);
+ }
+
+ private float GetProgress(EntityUid target, bool requireDead)
+ {
+ // deleted or gibbed or something, counts as dead
+ if (!TryComp<MindComponent>(target, out var mind) || mind.OwnedEntity == null)
+ return 1f;
+
+ // dead is success
+ if (_mind.IsCharacterDeadIc(mind))
+ return 1f;
+
+ // if the target has to be dead dead then don't check evac stuff
+ if (requireDead)
+ return 0f;
+
+ // if evac is disabled then they really do have to be dead
+ if (!_config.GetCVar(CCVars.EmergencyShuttleEnabled))
+ return 0f;
+
+ // target is escaping so you fail
+ if (_emergencyShuttle.IsTargetEscaping(mind.OwnedEntity.Value))
+ return 0f;
+
+ // evac has left without the target, greentext since the target is afk in space with a full oxygen tank and coordinates off.
+ if (_emergencyShuttle.ShuttlesLeft)
+ return 1f;
+
+ // if evac is still here and target hasn't boarded, show 50% to give you an indicator that you are doing good
+ return _emergencyShuttle.EmergencyShuttleArrived ? 0.5f : 0f;
+ }
+}
--- /dev/null
+using Content.Server.GameTicking.Rules;
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles requiring multiple traitors being alive for the objective to be given.
+/// </summary>
+public sealed class MultipleTraitorsRequirementSystem : EntitySystem
+{
+ [Dependency] private readonly TraitorRuleSystem _traitorRule = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<MultipleTraitorsRequirementComponent, RequirementCheckEvent>(OnCheck);
+ }
+
+ private void OnCheck(EntityUid uid, MultipleTraitorsRequirementComponent comp, ref RequirementCheckEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (_traitorRule.GetOtherTraitorMindsAliveAndConnected(args.Mind).Count < comp.Traitors)
+ args.Cancelled = true;
+ }
+}
--- /dev/null
+using Content.Server.Roles;
+using Content.Server.Objectives.Components;
+using Content.Server.Warps;
+using Content.Shared.Objectives.Components;
+using Robust.Shared.GameObjects;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles the objective conditions that hard depend on ninja.
+/// Survive is handled by <see cref="SurviveConditionSystem"/> since it works without being a ninja.
+/// </summary>
+public sealed class NinjaConditionsSystem : EntitySystem
+{
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly NumberObjectiveSystem _number = default!;
+
+ public override void Initialize()
+ {
+ SubscribeLocalEvent<DoorjackConditionComponent, ObjectiveGetProgressEvent>(OnDoorjackGetProgress);
+
+ SubscribeLocalEvent<SpiderChargeConditionComponent, ObjectiveAfterAssignEvent>(OnSpiderChargeAfterAssign);
+ SubscribeLocalEvent<SpiderChargeConditionComponent, ObjectiveGetProgressEvent>(OnSpiderChargeGetProgress);
+
+ SubscribeLocalEvent<StealResearchConditionComponent, ObjectiveGetProgressEvent>(OnStealResearchGetProgress);
+
+ SubscribeLocalEvent<TerrorConditionComponent, ObjectiveGetProgressEvent>(OnTerrorGetProgress);
+ }
+
+ // doorjack
+
+ private void OnDoorjackGetProgress(EntityUid uid, DoorjackConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = DoorjackProgress(args.MindId, _number.GetTarget(uid));
+ }
+
+ private float DoorjackProgress(EntityUid mindId, int target)
+ {
+ // prevent divide-by-zero
+ if (target == 0)
+ return 1f;
+
+ if (!TryComp<NinjaRoleComponent>(mindId, out var role))
+ return 0f;
+
+ if (role.DoorsJacked >= target)
+ return 1f;
+
+ return (float) role.DoorsJacked / (float) target;
+ }
+
+ // spider charge
+
+ private void OnSpiderChargeAfterAssign(EntityUid uid, SpiderChargeConditionComponent comp, ref ObjectiveAfterAssignEvent args)
+ {
+ _metaData.SetEntityName(uid, SpiderChargeTitle(args.MindId), args.Meta);
+ }
+
+ private void OnSpiderChargeGetProgress(EntityUid uid, SpiderChargeConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = TryComp<NinjaRoleComponent>(args.MindId, out var role) && role.SpiderChargeDetonated ? 1f : 0f;
+ }
+
+ private string SpiderChargeTitle(EntityUid mindId)
+ {
+ if (!TryComp<NinjaRoleComponent>(mindId, out var role) ||
+ role.SpiderChargeTarget == null ||
+ !TryComp<WarpPointComponent>(role.SpiderChargeTarget, out var warp) ||
+ warp.Location == null)
+ {
+ // this should never really happen but eh
+ return Loc.GetString("objective-condition-spider-charge-title-no-target");
+ }
+
+ return Loc.GetString("objective-condition-spider-charge-title", ("location", warp.Location));
+ }
+
+ // steal research
+
+ private void OnStealResearchGetProgress(EntityUid uid, StealResearchConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = StealResearchProgress(args.MindId, _number.GetTarget(uid));
+ }
+
+ private float StealResearchProgress(EntityUid mindId, int target)
+ {
+ // prevent divide-by-zero
+ if (target == 0)
+ return 1f;
+
+ if (!TryComp<NinjaRoleComponent>(mindId, out var role))
+ return 0f;
+
+ if (role.DownloadedNodes.Count >= target)
+ return 1f;
+
+ return (float) role.DownloadedNodes.Count / (float) target;
+ }
+
+ // terror
+
+ private void OnTerrorGetProgress(EntityUid uid, TerrorConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = TryComp<NinjaRoleComponent>(args.MindId, out var role) && role.CalledInThreat ? 1f : 0f;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles.Jobs;
+
+namespace Content.Server.Objectives.Systems;
+
+public sealed class NotCommandRequirementSystem : EntitySystem
+{
+ [Dependency] private readonly SharedJobSystem _job = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<NotCommandRequirementComponent, RequirementCheckEvent>(OnCheck);
+ }
+
+ private void OnCheck(EntityUid uid, NotCommandRequirementComponent comp, ref RequirementCheckEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ // cheap equivalent to checking that job department is command, since all command members require admin notification when leaving
+ if (_job.MindTryGetJob(args.MindId, out _, out var prototype) && prototype.RequireAdminNotify)
+ args.Cancelled = true;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles.Jobs;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles checking the job blacklist for this objective.
+/// </summary>
+public sealed class NotJobRequirementSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<NotJobRequirementComponent, RequirementCheckEvent>(OnCheck);
+ }
+
+ private void OnCheck(EntityUid uid, NotJobRequirementComponent comp, ref RequirementCheckEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ // if player has no job then don't care
+ if (!TryComp<JobComponent>(args.MindId, out var job))
+ return;
+
+ if (job.PrototypeId == comp.Job)
+ args.Cancelled = true;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Random;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Provides API for other components, handles picking the count and setting the title and description.
+/// </summary>
+public sealed class NumberObjectiveSystem : EntitySystem
+{
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<NumberObjectiveComponent, ObjectiveAssignedEvent>(OnAssigned);
+ SubscribeLocalEvent<NumberObjectiveComponent, ObjectiveAfterAssignEvent>(OnAfterAssign);
+ }
+
+ private void OnAssigned(EntityUid uid, NumberObjectiveComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ comp.Target = _random.Next(comp.Min, comp.Max);
+ }
+
+ private void OnAfterAssign(EntityUid uid, NumberObjectiveComponent comp, ref ObjectiveAfterAssignEvent args)
+ {
+ if (comp.Title != null)
+ _metaData.SetEntityName(uid, Loc.GetString(comp.Title, ("count", comp.Target)), args.Meta);
+
+ if (comp.Description != null)
+ _metaData.SetEntityDescription(uid, Loc.GetString(comp.Description, ("count", comp.Target)), args.Meta);
+ }
+
+ /// <summary>
+ /// Gets the objective's target count.
+ /// </summary>
+ public int GetTarget(EntityUid uid, NumberObjectiveComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp))
+ return 0;
+
+ return comp.Target;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles applying the objective component blacklist to the objective entity.
+/// </summary>
+public sealed class ObjectiveBlacklistRequirementSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<ObjectiveBlacklistRequirementComponent, RequirementCheckEvent>(OnCheck);
+ }
+
+ private void OnCheck(EntityUid uid, ObjectiveBlacklistRequirementComponent comp, ref RequirementCheckEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (comp.Blacklist.IsValid(uid, EntityManager))
+ args.Cancelled = true;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles role requirement for objectives that require a certain (probably antagonist) role(s).
+/// </summary>
+public sealed class RoleRequirementSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<RoleRequirementComponent, RequirementCheckEvent>(OnCheck);
+ }
+
+ private void OnCheck(EntityUid uid, RoleRequirementComponent comp, ref RequirementCheckEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ // this whitelist trick only works because roles are components on the mind and not entities
+ // if that gets reworked then this will need changing
+ if (!comp.Roles.IsValid(args.MindId, EntityManager))
+ args.Cancelled = true;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Server.Roles;
+using Content.Shared.Objectives.Components;
+
+namespace Content.Server.Objectives.Systems;
+
+public sealed class SpiderChargeTargetRequirementSystem : EntitySystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<SpiderChargeTargetRequirementComponent, RequirementCheckEvent>(OnCheck);
+ }
+
+ private void OnCheck(EntityUid uid, SpiderChargeTargetRequirementComponent comp, ref RequirementCheckEvent args)
+ {
+ if (args.Cancelled)
+ return;
+
+ if (!TryComp<NinjaRoleComponent>(args.MindId, out var role) || role.SpiderChargeTarget == null)
+ args.Cancelled = true;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Objectives.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Objectives.Systems;
+
+public sealed class StealConditionSystem : EntitySystem
+{
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
+
+ private EntityQuery<ContainerManagerComponent> containerQuery;
+ private EntityQuery<MetaDataComponent> metaQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ containerQuery = GetEntityQuery<ContainerManagerComponent>();
+ metaQuery = GetEntityQuery<MetaDataComponent>();
+
+ SubscribeLocalEvent<StealConditionComponent, ObjectiveAssignedEvent>(OnAssigned);
+ SubscribeLocalEvent<StealConditionComponent, ObjectiveAfterAssignEvent>(OnAfterAssign);
+ SubscribeLocalEvent<StealConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+ }
+
+ private void OnAssigned(EntityUid uid, StealConditionComponent comp, ref ObjectiveAssignedEvent args)
+ {
+ // cancel if the item to steal doesn't exist
+ args.Cancelled |= !_proto.HasIndex<EntityPrototype>(comp.Prototype);
+ }
+
+ private void OnAfterAssign(EntityUid uid, StealConditionComponent comp, ref ObjectiveAfterAssignEvent args)
+ {
+ var proto = _proto.Index<EntityPrototype>(comp.Prototype);
+ var title = comp.OwnerText == null
+ ? Loc.GetString("objective-condition-steal-title-no-owner", ("itemName", proto.Name))
+ : Loc.GetString("objective-condition-steal-title", ("owner", Loc.GetString(comp.OwnerText)), ("itemName", proto.Name));
+ var description = Loc.GetString("objective-condition-steal-description", ("itemName", proto.Name));
+
+ _metaData.SetEntityName(uid, title, args.Meta);
+ _metaData.SetEntityDescription(uid, description, args.Meta);
+ _objectives.SetIcon(uid, new SpriteSpecifier.EntityPrototype(comp.Prototype), args.Objective);
+ }
+
+ private void OnGetProgress(EntityUid uid, StealConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = GetProgress(args.Mind, comp.Prototype);
+ }
+
+ private float GetProgress(MindComponent mind, string prototype)
+ {
+ // TODO make this a container system function
+ // or: just iterate through transform children, instead of containers?
+
+ if (!metaQuery.TryGetComponent(mind.OwnedEntity, out var meta))
+ return 0;
+
+ // who added this check bruh
+ if (meta.EntityPrototype?.ID == prototype)
+ return 1;
+
+ if (!containerQuery.TryGetComponent(mind.OwnedEntity, out var currentManager))
+ return 0;
+
+ // recursively check each container for the item
+ // checks inventory, bag, implants, etc.
+ var stack = new Stack<ContainerManagerComponent>();
+ do
+ {
+ foreach (var container in currentManager.Containers.Values)
+ {
+ foreach (var entity in container.ContainedEntities)
+ {
+ // check if this is the item
+ if (metaQuery.GetComponent(entity).EntityPrototype?.ID == prototype)
+ return 1;
+
+ // if it is a container check its contents
+ if (containerQuery.TryGetComponent(entity, out var containerManager))
+ stack.Push(containerManager);
+ }
+ }
+ } while (stack.TryPop(out currentManager));
+
+ return 0;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Mind;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Handles progress for the survive objective condition.
+/// </summary>
+public sealed class SurviveConditionSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<SurviveConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
+ }
+
+ private void OnGetProgress(EntityUid uid, SurviveConditionComponent comp, ref ObjectiveGetProgressEvent args)
+ {
+ args.Progress = _mind.IsCharacterDeadIc(args.Mind) ? 0f : 1f;
+ }
+}
--- /dev/null
+using Content.Server.Objectives.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.GameObjects;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Content.Server.Objectives.Systems;
+
+/// <summary>
+/// Provides API for other components and handles setting the title.
+/// </summary>
+public sealed class TargetObjectiveSystem : EntitySystem
+{
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly SharedJobSystem _job = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent<TargetObjectiveComponent, ObjectiveAfterAssignEvent>(OnAfterAssign);
+ }
+
+ private void OnAfterAssign(EntityUid uid, TargetObjectiveComponent comp, ref ObjectiveAfterAssignEvent args)
+ {
+ if (!GetTarget(uid, out var target, comp))
+ return;
+
+ _metaData.SetEntityName(uid, GetTitle(target.Value, comp.Title), args.Meta);
+ }
+
+ /// <summary>
+ /// Sets the Target field for the title and other components to use.
+ /// </summary>
+ public void SetTarget(EntityUid uid, EntityUid target, TargetObjectiveComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp))
+ return;
+
+ comp.Target = target;
+ }
+
+ /// <summary>
+ /// Gets the target from the component.
+ /// </summary>
+ /// <remarks>
+ /// If it is null then the prototype is invalid, just return.
+ /// </remarks>
+ public bool GetTarget(EntityUid uid, [NotNullWhen(true)] out EntityUid? target, TargetObjectiveComponent? comp = null)
+ {
+ target = Resolve(uid, ref comp) ? comp.Target : null;
+ return target != null;
+ }
+
+ private string GetTitle(EntityUid target, string title)
+ {
+ var targetName = "Unknown";
+ if (TryComp<MindComponent>(target, out var mind) && mind.CharacterName != null)
+ {
+ targetName = mind.CharacterName;
+ }
+
+ var jobName = _job.MindTryGetJobName(target);
+ return Loc.GetString(title, ("targetName", targetName), ("job", jobName));
+ }
+
+}
{
public readonly NetEntity NetEntity;
public readonly string JobTitle;
- public readonly Dictionary<string, List<ConditionInfo>> Objectives;
+ public readonly Dictionary<string, List<ObjectiveInfo>> Objectives;
public readonly string? Briefing;
- public CharacterInfoEvent(NetEntity netEntity, string jobTitle, Dictionary<string, List<ConditionInfo>> objectives, string? briefing)
+ public CharacterInfoEvent(NetEntity netEntity, string jobTitle, Dictionary<string, List<ObjectiveInfo>> objectives, string? briefing)
{
NetEntity = netEntity;
JobTitle = jobTitle;
using Content.Shared.GameTicking;
using Content.Shared.Mind.Components;
-using Content.Shared.Objectives;
using Robust.Shared.Network;
using Robust.Shared.Players;
[RegisterComponent]
public sealed partial class MindComponent : Component
{
- internal readonly List<Objective> Objectives = new();
+ internal readonly List<EntityUid> Objectives = new();
/// <summary>
/// The session ID of the player owning this mind.
// TODO move objectives out of mind component
/// <summary>
- /// An enumerable over all the objectives this mind has.
+ /// An enumerable over all the objective entities this mind has.
/// </summary>
[ViewVariables]
- public IEnumerable<Objective> AllObjectives => Objectives;
+ public IEnumerable<EntityUid> AllObjectives => Objectives;
/// <summary>
/// Prevents user from ghosting out
using Content.Shared.Database;
using Content.Shared.Examine;
using Content.Shared.GameTicking;
+using Content.Shared.Humanoid;
using Content.Shared.Interaction.Events;
using Content.Shared.Mind.Components;
using Content.Shared.Mobs.Components;
using Content.Shared.Mobs.Systems;
using Content.Shared.Objectives;
+using Content.Shared.Objectives.Systems;
using Content.Shared.Players;
using Robust.Shared.Map;
using Robust.Shared.Network;
public abstract class SharedMindSystem : EntitySystem
{
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly IPrototypeManager _proto = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- [Dependency] private readonly SharedPlayerSystem _playerSystem = default!;
+ [Dependency] private readonly MobStateSystem _mobState = default!;
+ [Dependency] private readonly SharedObjectivesSystem _objectives = default!;
+ [Dependency] private readonly SharedPlayerSystem _player = default!;
// This is dictionary is required to track the minds of disconnected players that may have had their entity deleted.
protected readonly Dictionary<NetUserId, EntityUid> UserMinds = new();
if (!mindContainer.ShowExamineInfo || !args.IsInDetailsRange)
return;
- var dead = _mobStateSystem.IsDead(uid);
+ var dead = _mobState.IsDead(uid);
var hasSession = CompOrNull<MindComponent>(mindContainer.Mind)?.Session;
if (dead && !mindContainer.HasMind)
if (targetMobState == null)
return true;
// They might actually be alive.
- return _mobStateSystem.IsDead(mind.OwnedEntity.Value, targetMobState);
+ return _mobState.IsDead(mind.OwnedEntity.Value, targetMobState);
}
public virtual void Visit(EntityUid mindId, EntityUid entity, MindComponent? mind = null)
public void WipeMind(ICommonSession player)
{
- var mind = _playerSystem.ContentData(player)?.Mind;
+ var mind = _player.ContentData(player)?.Mind;
DebugTools.Assert(GetMind(player.UserId) == mind);
WipeMind(mind);
}
}
/// <summary>
- /// Adds an objective to this mind.
+ /// Tries to create and add an objective from its prototype id.
/// </summary>
- public bool TryAddObjective(EntityUid mindId, MindComponent mind, ObjectivePrototype objectivePrototype)
+ /// <returns>Returns true if adding the objective succeeded.</returns>
+ public bool TryAddObjective(EntityUid mindId, MindComponent mind, string proto)
{
- if (!objectivePrototype.CanBeAssigned(mindId, mind))
+ var objective = _objectives.TryCreateObjective(mindId, mind, proto);
+ if (objective == null)
return false;
- var objective = objectivePrototype.GetObjective(mindId, mind);
- if (mind.Objectives.Contains(objective))
- return false;
-
- foreach (var condition in objective.Conditions)
- {
- _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' added to mind of {MindOwnerLoggingString(mind)}");
- }
- mind.Objectives.Add(objective);
+ AddObjective(mindId, mind, objective.Value);
return true;
}
/// <summary>
- /// Adds an objective, by id, to this mind.
+ /// Adds an objective that already exists, and is assumed to have had its requirements checked.
/// </summary>
- public bool TryAddObjective(EntityUid mindId, string name, MindComponent? mind = null)
+ public void AddObjective(EntityUid mindId, MindComponent mind, EntityUid objective)
{
- if (!Resolve(mindId, ref mind))
- return false;
-
- if (!_proto.TryIndex<ObjectivePrototype>(name, out var objective))
- {
- Log.Error($"Tried to add unknown objective prototype: {name}");
- return false;
- }
-
- return TryAddObjective(mindId, mind, objective);
+ var title = Name(objective);
+ _adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) added to mind of {MindOwnerLoggingString(mind)}");
+ mind.Objectives.Add(objective);
}
/// <summary>
- /// Removes an objective to this mind.
+ /// Removes an objective from this mind.
/// </summary>
/// <returns>Returns true if the removal succeeded.</returns>
- public bool TryRemoveObjective(MindComponent mind, int index)
+ public bool TryRemoveObjective(EntityUid mindId, MindComponent mind, int index)
{
if (index < 0 || index >= mind.Objectives.Count)
return false;
var objective = mind.Objectives[index];
- foreach (var condition in objective.Conditions)
- {
- _adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' removed from the mind of {MindOwnerLoggingString(mind)}");
- }
-
+ var title = Name(objective);
+ _adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) removed from the mind of {MindOwnerLoggingString(mind)}");
mind.Objectives.Remove(objective);
+ Del(objective);
return true;
}
{
mindId = default;
mind = null;
- return _playerSystem.ContentData(player) is { } data && TryGetMind(data, out mindId, out mind);
+ return _player.ContentData(player) is { } data && TryGetMind(data, out mindId, out mind);
}
/// <summary>
{
return TryGetMind(userId, out _, out var mind) ? mind.CharacterName : null;
}
+
+ /// <summary>
+ /// Returns a list of every living humanoid player's minds, except for a single one which is exluded.
+ /// </summary>
+ public List<EntityUid> GetAliveHumansExcept(EntityUid exclude)
+ {
+ var mindQuery = EntityQuery<MindComponent>();
+
+ var allHumans = new List<EntityUid>();
+ // HumanoidAppearanceComponent is used to prevent mice, pAIs, etc from being chosen
+ var query = EntityQueryEnumerator<MindContainerComponent, MobStateComponent, HumanoidAppearanceComponent>();
+ while (query.MoveNext(out var uid, out var mc, out var mobState, out _))
+ {
+ // the player needs to have a mind and not be the excluded one
+ if (mc.Mind == null || mc.Mind == exclude)
+ continue;
+
+ // the player has to be alive
+ if (_mobState.IsAlive(uid, mobState))
+ allHumans.Add(mc.Mind.Value);
+ }
+
+ return allHumans;
+ }
}
/// <summary>
--- /dev/null
+using Content.Shared.Mind;
+using Content.Shared.Objectives;
+using Content.Shared.Objectives.Systems;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Objectives.Components;
+
+/// <summary>
+/// Required component for an objective entity prototype.
+/// </summary>
+[RegisterComponent, Access(typeof(SharedObjectivesSystem))]
+public sealed partial class ObjectiveComponent : Component
+{
+ /// <summary>
+ /// Difficulty rating used to avoid assigning too many difficult objectives.
+ /// </summary>
+ [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ public float Difficulty;
+
+ /// <summary>
+ /// Organisation that issued this objective, used for grouping and as a header above common objectives.
+ /// </summary>
+ [DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
+ public string Issuer = string.Empty;
+
+ /// <summary>
+ /// Unique objectives can only have 1 per prototype id.
+ /// Set this to false if you want multiple objectives of the same prototype.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public bool Unique = true;
+
+ /// <summary>
+ /// Icon of this objective to display in the character menu.
+ /// Can be specified by an <see cref="ObjectiveGetInfoEvent"/> handler but is usually done in the prototype.
+ /// </summary>
+ [DataField, ViewVariables(VVAccess.ReadWrite)]
+ public SpriteSpecifier? Icon;
+}
+
+/// <summary>
+/// Event raised on an objective after spawning it to see if it meets all the requirements.
+/// Requirement components should have subscriptions and cancel if the requirements are not met.
+/// If a requirement is not met then the objective is deleted.
+/// </summary>
+[ByRefEvent]
+public record struct RequirementCheckEvent(EntityUid MindId, MindComponent Mind, bool Cancelled = false);
+
+/// <summary>
+/// Event raised on an objective after its requirements have been checked.
+/// If <see cref="Cancelled"/> is set to true, the objective is deleted.
+/// Use this if the objective cannot be used, like a kill objective with no people alive.
+/// </summary>
+[ByRefEvent]
+public record struct ObjectiveAssignedEvent(EntityUid MindId, MindComponent Mind, bool Cancelled = false);
+
+/// <summary>
+/// Event raised on an objective after everything has handled <see cref="ObjectiveAssignedEvent"/>.
+/// Use this to set the objective's title description or icon.
+/// </summary>
+[ByRefEvent]
+public record struct ObjectiveAfterAssignEvent(EntityUid MindId, MindComponent Mind, ObjectiveComponent Objective, MetaDataComponent Meta);
+
+/// <summary>
+/// Event raised on an objective to update the Progress field.
+/// To use this yourself call <see cref="SharedObjectivesSystem.GetInfo"/> with the mind.
+/// </summary>
+[ByRefEvent]
+public record struct ObjectiveGetProgressEvent(EntityUid MindId, MindComponent Mind, float? Progress = null);
+++ /dev/null
-using Robust.Shared.Serialization;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.Objectives
-{
- [Serializable, NetSerializable]
- public sealed class ConditionInfo
- {
- public string Title { get; }
- public string Description { get; }
- public SpriteSpecifier SpriteSpecifier { get; }
- public float Progress { get; }
-
- public ConditionInfo(string title, string description, SpriteSpecifier spriteSpecifier, float progress)
- {
- Title = title;
- Description = description;
- SpriteSpecifier = spriteSpecifier;
- Progress = progress;
- }
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-using Robust.Shared.Utility;
-
-namespace Content.Shared.Objectives.Interfaces
-{
- // TODO refactor all of this to be ecs
- public interface IObjectiveCondition
- {
- /// <summary>
- /// Returns a copy of the IObjectiveCondition which is assigned to the mind.
- /// </summary>
- /// <param name="mindId">Mind id to assign to.</param>
- /// <param name="mind">Mind to assign to.</param>
- /// <returns>The new IObjectiveCondition.</returns>
- IObjectiveCondition GetAssigned(EntityUid mindId, MindComponent mind);
-
- /// <summary>
- /// Returns the title of the condition.
- /// </summary>
- string Title { get; }
-
- /// <summary>
- /// Returns the description of the condition.
- /// </summary>
- string Description { get; }
-
- /// <summary>
- /// Returns a SpriteSpecifier to be used as an icon for the condition.
- /// </summary>
- SpriteSpecifier Icon { get; }
-
- /// <summary>
- /// Returns the current progress of the condition in % from 0 to 1.
- /// </summary>
- /// <returns>Current progress in %.</returns>
- float Progress { get; }
-
- /// <summary>
- /// Returns a difficulty of the condition.
- /// </summary>
- float Difficulty { get; }
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-
-namespace Content.Shared.Objectives.Interfaces
-{
- // TODO refactor all of this to be ecs
- public interface IObjectiveRequirement
- {
- /// <summary>
- /// Checks whether or not the entity & its surroundings are valid to be given the objective.
- /// </summary>
- /// <returns>Returns true if objective can be given.</returns>
- bool CanBeAssigned(EntityUid mindId, MindComponent mind);
- }
-}
+++ /dev/null
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-
-namespace Content.Shared.Objectives
-{
- public sealed class Objective : IEquatable<Objective>
- {
- [ViewVariables]
- public readonly EntityUid MindId;
- [ViewVariables]
- public readonly MindComponent Mind;
- [ViewVariables]
- public readonly ObjectivePrototype Prototype;
- private readonly List<IObjectiveCondition> _conditions = new();
- [ViewVariables]
- public IReadOnlyList<IObjectiveCondition> Conditions => _conditions;
-
- public Objective(ObjectivePrototype prototype, EntityUid mindId, MindComponent mind)
- {
- Prototype = prototype;
- MindId = mindId;
- Mind = mind;
- foreach (var condition in prototype.Conditions)
- {
- _conditions.Add(condition.GetAssigned(mindId, mind));
- }
- }
-
- public bool Equals(Objective? other)
- {
- if (other is null) return false;
- if (ReferenceEquals(this, other)) return true;
- if (!Equals(Mind, other.Mind) || !Equals(Prototype, other.Prototype)) return false;
- if (_conditions.Count != other._conditions.Count) return false;
- for (var i = 0; i < _conditions.Count; i++)
- {
- if (!_conditions[i].Equals(other._conditions[i])) return false;
- }
-
- return true;
- }
-
- public override bool Equals(object? obj)
- {
- if (ReferenceEquals(null, obj)) return false;
- if (ReferenceEquals(this, obj)) return true;
- if (obj.GetType() != GetType()) return false;
- return Equals((Objective) obj);
- }
-
- public override int GetHashCode()
- {
- return HashCode.Combine(Mind, Prototype, _conditions);
- }
- }
-}
--- /dev/null
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Objectives;
+
+/// <summary>
+/// Info about objectives visible in the character menu and on round end.
+/// Description and icon are displayed only in the character menu.
+/// Progress is a percentage from 0.0 to 1.0.
+/// </summary>
+/// <remarks>
+/// All of these fields must eventually be set by condition event handlers.
+/// Everything but progress can be set to static data in yaml on the entity and <see cref="ObjectiveComponent"/>.
+/// If anything is null it will be logged and return null.
+/// </remarks>
+[Serializable, NetSerializable]
+public record struct ObjectiveInfo(string Title, string Description, SpriteSpecifier Icon, float Progress);
+++ /dev/null
-using System.Linq;
-using Content.Shared.Mind;
-using Content.Shared.Objectives.Interfaces;
-using Robust.Shared.Prototypes;
-
-namespace Content.Shared.Objectives
-{
- /// <summary>
- /// Prototype for objectives. Remember that to be assigned, it should be added to one or more objective groups in prototype. E.g. crew, traitor, wizard
- /// </summary>
- [Prototype("objective")]
- public sealed class ObjectivePrototype : IPrototype
- {
- [ViewVariables]
- [IdDataField]
- public string ID { get; private set; } = default!;
-
- [DataField("issuer")] public string Issuer { get; private set; } = "Unknown";
-
- [ViewVariables]
- public float Difficulty => _difficultyOverride ?? _conditions.Sum(c => c.Difficulty);
-
- [DataField("conditions", serverOnly: true)]
- private List<IObjectiveCondition> _conditions = new();
- [DataField("requirements")]
- private List<IObjectiveRequirement> _requirements = new();
-
- [ViewVariables]
- public IReadOnlyList<IObjectiveCondition> Conditions => _conditions;
-
- [DataField("canBeDuplicate")]
- public bool CanBeDuplicateAssignment { get; private set; }
-
- [ViewVariables(VVAccess.ReadWrite)]
- [DataField("difficultyOverride")]
- private float? _difficultyOverride = null;
-
- public bool CanBeAssigned(EntityUid mindId, MindComponent mind)
- {
- foreach (var requirement in _requirements)
- {
- if (!requirement.CanBeAssigned(mindId, mind))
- return false;
- }
-
- if (!CanBeDuplicateAssignment)
- {
- foreach (var objective in mind.AllObjectives)
- {
- if (objective.Prototype.ID == ID)
- return false;
- }
- }
-
- return true;
- }
-
- public Objective GetObjective(EntityUid mindId, MindComponent mind)
- {
- return new Objective(this, mindId, mind);
- }
- }
-}
--- /dev/null
+using Content.Shared.Mind;
+using Content.Shared.Objectives;
+using Content.Shared.Objectives.Components;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Objectives.Systems;
+
+/// <summary>
+/// Provides API for creating and interacting with objectives.
+/// </summary>
+public abstract class SharedObjectivesSystem : EntitySystem
+{
+ [Dependency] private readonly SharedMindSystem _mind = default!;
+
+ private EntityQuery<MetaDataComponent> _metaQuery;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _metaQuery = GetEntityQuery<MetaDataComponent>();
+ }
+
+ /// <summary>
+ /// Checks requirements and duplicate objectives to see if an objective can be assigned.
+ /// </summary>
+ public bool CanBeAssigned(EntityUid uid, EntityUid mindId, MindComponent mind, ObjectiveComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp))
+ return false;
+
+ var ev = new RequirementCheckEvent(mindId, mind);
+ RaiseLocalEvent(uid, ref ev);
+ if (ev.Cancelled)
+ return false;
+
+ // only check for duplicate prototypes if it's unique
+ if (comp.Unique)
+ {
+ var proto = _metaQuery.GetComponent(uid).EntityPrototype?.ID;
+ foreach (var objective in mind.AllObjectives)
+ {
+ if (_metaQuery.GetComponent(objective).EntityPrototype?.ID == proto)
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /// <summary>
+ /// Spawns and assigns an objective for a mind.
+ /// The objective is not added to the mind's objectives, mind system does that in TryAddObjective.
+ /// If the objective could not be assigned the objective is deleted and null is returned.
+ /// </summary>
+ public EntityUid? TryCreateObjective(EntityUid mindId, MindComponent mind, string proto)
+ {
+ var uid = Spawn(proto);
+ if (!TryComp<ObjectiveComponent>(uid, out var comp))
+ {
+ Del(uid);
+ Log.Error($"Invalid objective prototype {proto}, missing ObjectiveComponent");
+ return null;
+ }
+
+ Log.Debug($"Created objective {proto} ({uid})");
+
+ if (!CanBeAssigned(uid, mindId, mind, comp))
+ {
+ Del(uid);
+ Log.Warning($"Objective {uid} did not match the requirements for {_mind.MindOwnerLoggingString(mind)}, deleted it");
+ return null;
+ }
+
+ var ev = new ObjectiveAssignedEvent(mindId, mind);
+ RaiseLocalEvent(uid, ref ev);
+ if (ev.Cancelled)
+ {
+ Del(uid);
+ Log.Warning($"Could not assign objective {uid}, deleted it");
+ return null;
+ }
+
+ // let the title description and icon be set by systems
+ var afterEv = new ObjectiveAfterAssignEvent(mindId, mind, comp, MetaData(uid));
+ RaiseLocalEvent(uid, ref afterEv);
+
+ return uid;
+ }
+
+ /// <summary>
+ /// Get the title, description, icon and progress of an objective using <see cref="ObjectiveGetInfoEvent"/>.
+ /// If any of them are null it is logged and null is returned.
+ /// </summary>
+ /// <param name="uid"/>ID of the condition entity</param>
+ /// <param name="mindId"/>ID of the player's mind entity</param>
+ /// <param name="mind"/>Mind component of the player's mind</param>
+ public ObjectiveInfo? GetInfo(EntityUid uid, EntityUid mindId, MindComponent? mind = null)
+ {
+ if (!Resolve(mindId, ref mind))
+ return null;
+
+ var ev = new ObjectiveGetProgressEvent(mindId, mind);
+ RaiseLocalEvent(uid, ref ev);
+
+ var comp = Comp<ObjectiveComponent>(uid);
+ var meta = MetaData(uid);
+ var title = meta.EntityName;
+ var description = meta.EntityDescription;
+ if (comp.Icon == null || ev.Progress == null)
+ {
+ Log.Error($"An objective {ToPrettyString(uid):objective} of {_mind.MindOwnerLoggingString(mind)} is missing icon or progress ({ev.Progress})");
+ return null;
+ }
+
+ return new ObjectiveInfo(title, description, comp.Icon, ev.Progress.Value);
+ }
+
+ /// <summary>
+ /// Sets the objective's icon to the one specified.
+ /// Intended for <see cref="ObjectiveAfterAssignEvent"/> handlers to set an icon.
+ /// </summary>
+ public void SetIcon(EntityUid uid, SpriteSpecifier icon, ObjectiveComponent? comp = null)
+ {
+ if (!Resolve(uid, ref comp))
+ return;
+
+ comp.Icon = icon;
+ }
+}
+++ /dev/null
-objective-condition-die-title = Die a glorious death
-objective-condition-die-description = Die.
\ No newline at end of file
objective-condition-doorjack-title = Doorjack {$count} doors on the station.
-objective-condition-doorjack-description = Your gloves can emag airlocks. Do this {$count} doors on the station.
+objective-condition-doorjack-description = Your gloves can emag airlocks. Do this to {$count} doors on the station.
+++ /dev/null
-objective-condition-escape-shuttle-title = Escape to centcom alive and unrestrained.
-objective-condition-escape-shuttle-description = One of our undercover agents will debrief you when you arrive. Don't show up in cuffs.
+++ /dev/null
-objective-condition-kill-head-description = We need this head gone and you probably know why. Good luck, agent.
--- /dev/null
+objective-condition-kill-head-title = Kill {$targetName}, {CAPITALIZE($job)}
+++ /dev/null
-objective-condition-kill-person-title = Kill or maroon {$targetName}, {CAPITALIZE($job)}
-objective-condition-kill-person-description = Do it however you like, just make sure they don't make it to centcom.
--- /dev/null
+objective-condition-kill-person-title = Kill or maroon {$targetName}, {CAPITALIZE($job)}
objective-condition-other-traitor-alive-title = Ensure fellow traitor {$targetName}, {CAPITALIZE($job)} stays alive.
-objective-condition-other-traitor-alive-description = Identify yourself at your own risk. We just need them alive.
objective-condition-other-traitor-progress-title = Ensure fellow traitor {$targetName}, {CAPITALIZE($job)} achieves at least half their objectives.
-objective-condition-other-traitor-progress-description = Identify yourself at your own risk. We just need them to succeed.
+++ /dev/null
-objective-condition-spider-charge-title = Detonate the spider clan charge in {$location}
-objective-condition-spider-charge-no-target = Detonate the spider clan charge... somewhere?
-objective-condition-spider-charge-description = This bomb can be detonated in a specific location. Note that the bomb will not work anywhere else!
--- /dev/null
+objective-condition-spider-charge-title = Detonate the spider clan charge in {$location}
+++ /dev/null
-objective-condition-steal-research-title = Steal {$count} technologies.
-objective-condition-steal-research-description = Your gloves can be used to hack a research server and steal its precious data. If science has been slacking you'll have to get to work.
--- /dev/null
+objective-condition-steal-research-title = Steal {$count} technologies.
+++ /dev/null
-objective-condition-survive-title = Survive
-objective-condition-survive-description = You wouldn't be a very good ninja if you died, now would you?
+++ /dev/null
-objective-condition-terror-title = Call in a threat
-objective-condition-terror-description = Use your gloves on a communication console in order to bring another threat to the station.
objectives-no-objectives = {$title} was a {$agent}.
objectives-with-objectives = {$title} was a {$agent} who had the following objectives:
-objectives-condition-success = {$condition} | [color={$markupColor}]Success![/color]
-objectives-condition-fail = {$condition} | [color={$markupColor}]Failure![/color] ({$progress}%)
+objectives-objective-success = {$objective} | [color={$markupColor}]Success![/color]
+objectives-objective-fail = {$objective} | [color={$markupColor}]Failure![/color] ({$progress}%)
- DoorjackObjective
- SpiderChargeObjective
- TerrorObjective
- - SurviveObjective
+ - NinjaSurviveObjective
threats:
- announcement: terror-dragon
rule: Dragon
--- /dev/null
+# OBJECTIVE STYLE
+# in comments anything that says final prototype means the objective that isnt abstract
+# the final prototype must be noSpawn to avoid showing in f5
+# components are listed in this order:
+# 1. Objective
+# 2. requirement components
+# 3. non-condition components
+# 4. the condition component
+
+# all objectives should inherit this at some point
+# then have its difficulty etc fields set in the final objective prototypes
+- type: entity
+ abstract: true
+ id: BaseObjective
+ components:
+ - type: Objective
+
+# requires that the player not have a die objective
+- type: entity
+ abstract: true
+ parent: BaseObjective
+ id: BaseLivingObjective
+ components:
+ - type: ObjectiveBlacklistRequirement
+ blacklist:
+ components:
+ - DieCondition
+
+# objective that targets a player
+# final prototype must specify the title locale id in TargetObjective
+- type: entity
+ abstract: true
+ parent: BaseObjective
+ id: BaseTargetObjective
+ components:
+ - type: TargetObjective
+
+# requires that the player kill someone
+# disables social objectives and is disabled by social objectives
+- type: entity
+ abstract: true
+ parent: BaseTargetObjective
+ id: BaseKillObjective
+ components:
+ - type: Objective
+ unique: false
+ icon:
+ sprite: Objects/Weapons/Guns/Pistols/viper.rsi
+ state: icon
+ - type: ObjectiveBlacklistRequirement
+ blacklist:
+ components:
+ - SocialObjective
+ - type: KillPersonCondition
+
+# requires that the player interact socially with someone
+# disables kill objectives and is disabled by kill objectives
+- type: entity
+ abstract: true
+ parent: BaseTargetObjective
+ id: BaseSocialObjective
+ components:
+ - type: Objective
+ unique: false
+ - type: ObjectiveBlacklistRequirement
+ blacklist:
+ components:
+ - KillPersonCondition
+ - type: SocialObjective
+
+# requires that the target survives the round
+- type: entity
+ abstract: true
+ parent: BaseSocialObjective
+ id: BaseKeepAliveObjective
+ components:
+ - type: KeepAliveCondition
+
+# requires that the target completes at least 50% of their objectives
+- type: entity
+ abstract: true
+ parent: BaseSocialObjective
+ id: BaseHelpProgressObjective
+ components:
+ - type: HelpProgressCondition
+
+# requires that the player steal an item specified in the final prototype
+- type: entity
+ abstract: true
+ parent: BaseLivingObjective
+ id: BaseStealObjective
+ components:
+ - type: StealCondition
+
+# requires that the player not die, ignores being on emergency shuttle or cuffed
+- type: entity
+ abstract: true
+ parent: BaseObjective
+ id: BaseSurviveObjective
+ components:
+ - type: SurviveCondition
--- /dev/null
+- type: entity
+ abstract: true
+ parent: BaseObjective
+ id: BaseNinjaObjective
+ components:
+ - type: Objective
+ # difficulty isn't used all since objectives are picked
+ difficulty: 1.5
+ issuer: spiderclan
+ - type: RoleRequirement
+ roles:
+ components:
+ - NinjaRole
+
+- type: entity
+ noSpawn: true
+ parent: BaseNinjaObjective
+ id: DoorjackObjective
+ components:
+ - type: Objective
+ icon:
+ sprite: Objects/Tools/emag.rsi
+ state: icon
+ - type: NumberObjective
+ min: 15
+ max: 40
+ title: objective-condition-doorjack-title
+ description: objective-condition-doorjack-description
+ - type: DoorjackCondition
+
+- type: entity
+ noSpawn: true
+ parent: BaseNinjaObjective
+ id: StealResearchObjective
+ description: Your gloves can be used to hack a research server and steal its precious data. If science has been slacking you'll have to get to work.
+ components:
+ - type: Objective
+ icon:
+ sprite: Structures/Machines/server.rsi
+ state: server
+ - type: NumberObjective
+ min: 5
+ max: 10
+ title: objective-condition-steal-research-title
+ - type: StealResearchCondition
+
+- type: entity
+ noSpawn: true
+ parent: BaseNinjaObjective
+ id: SpiderChargeObjective
+ description: This bomb can be detonated in a specific location. Note that the bomb will not work anywhere else!
+ components:
+ - type: Objective
+ icon:
+ sprite: Objects/Weapons/Bombs/spidercharge.rsi
+ state: icon
+ - type: SpiderChargeTargetRequirement
+ - type: SpiderChargeCondition
+
+- type: entity
+ noSpawn: true
+ parent: [BaseNinjaObjective, BaseSurviveObjective]
+ id: NinjaSurviveObjective
+ name: Survive
+ description: You wouldn't be a very good ninja if you died, now would you?
+ components:
+ - type: Objective
+ icon:
+ sprite: Clothing/Mask/ninja.rsi
+ state: icon
+
+- type: entity
+ noSpawn: true
+ parent: BaseNinjaObjective
+ id: TerrorObjective
+ name: Call in a threat
+ description: Use your gloves on a communication console in order to bring another threat to the station.
+ components:
+ - type: Objective
+ icon:
+ sprite: Objects/Fun/Instruments/otherinstruments.rsi
+ state: red_phone
+ - type: TerrorCondition
+++ /dev/null
-- type: objective
- id: StealResearchObjective
- issuer: spiderclan
- requirements:
- - !type:NinjaRequirement {}
- conditions:
- - !type:StealResearchCondition {}
-
-- type: objective
- id: DoorjackObjective
- issuer: spiderclan
- requirements:
- - !type:NinjaRequirement {}
- conditions:
- - !type:DoorjackCondition {}
-
-- type: objective
- id: SpiderChargeObjective
- issuer: spiderclan
- requirements:
- - !type:NinjaRequirement {}
- - !type:SpiderChargeTargetRequirement {}
- conditions:
- - !type:SpiderChargeCondition {}
-
-- type: objective
- id: TerrorObjective
- issuer: spiderclan
- requirements:
- - !type:NinjaRequirement {}
- conditions:
- - !type:TerrorCondition {}
-
-- type: objective
- id: SurviveObjective
- issuer: spiderclan
- requirements:
- - !type:NinjaRequirement {}
- conditions:
- - !type:SurviveCondition {}
- type: weightedRandom
id: TraitorObjectiveGroupKill
weights:
- KillRandomObjective: 1
+ KillRandomPersonObjective: 1
KillRandomHeadObjective: 0.25
- type: weightedRandom
--- /dev/null
+- type: entity
+ abstract: true
+ parent: BaseObjective
+ id: BaseTraitorObjective
+ components:
+ - type: Objective
+ issuer: syndicate
+ - type: RoleRequirement
+ roles:
+ components:
+ - TraitorRole
+
+- type: entity
+ abstract: true
+ parent: [BaseTraitorObjective, BaseSocialObjective]
+ id: BaseTraitorSocialObjective
+ components:
+ - type: Objective
+ icon:
+ sprite: Objects/Misc/bureaucracy.rsi
+ state: folder-white
+ - type: MultipleTraitorsRequirement
+
+- type: entity
+ abstract: true
+ parent: [BaseTraitorObjective, BaseStealObjective]
+ id: BaseTraitorStealObjective
+ components:
+ - type: Objective
+ difficulty: 2.75
+
+# state
+
+- type: entity
+ noSpawn: true
+ parent: [BaseTraitorObjective, BaseLivingObjective]
+ id: EscapeShuttleObjective
+ name: Escape to centcom alive and unrestrained.
+ description: One of our undercover agents will debrief you when you arrive. Don't show up in cuffs.
+ components:
+ - type: Objective
+ difficulty: 1.3
+ icon:
+ sprite: Structures/Furniture/chairs.rsi
+ state: shuttle
+ - type: EscapeShuttleCondition
+
+- type: entity
+ noSpawn: true
+ parent: BaseTraitorObjective
+ id: DieObjective
+ name: Die a glorious death
+ description: Die.
+ components:
+ - type: Objective
+ difficulty: 0.5
+ icon:
+ sprite: Mobs/Ghosts/ghost_human.rsi
+ state: icon
+ - type: ObjectiveBlacklistRequirement
+ blacklist:
+ components:
+ - EscapeShuttleCondition
+ - StealCondition
+ - type: DieCondition
+
+# kill
+
+- type: entity
+ noSpawn: true
+ parent: [BaseTraitorObjective, BaseKillObjective]
+ id: KillRandomPersonObjective
+ description: Do it however you like, just make sure they don't make it to centcom.
+ components:
+ - type: Objective
+ difficulty: 1.75
+ unique: false
+ - type: TargetObjective
+ title: objective-condition-kill-person-title
+ - type: PickRandomPerson
+
+- type: entity
+ noSpawn: true
+ parent: [BaseTraitorObjective, BaseKillObjective]
+ id: KillRandomHeadObjective
+ description: We need this head gone and you probably know why. Good luck, agent.
+ components:
+ - type: Objective
+ # technically its still possible for KillRandomPersonObjective to roll a head but this is guaranteed, so higher difficulty
+ difficulty: 3.0
+ # killing 1 head is enough
+ unique: true
+ - type: TargetObjective
+ title: objective-condition-kill-head-title
+ - type: PickRandomHead
+ - type: KillPersonCondition
+ # don't count missing evac as killing as heads are higher profile, so you really need to do the dirty work
+ # if ce flies a shittle to centcom you better find a way onto it
+ requireDead: true
+
+# social
+
+- type: entity
+ noSpawn: true
+ parent: [BaseTraitorSocialObjective, BaseKeepAliveObjective]
+ id: RandomTraitorAliveObjective
+ description: Identify yourself at your own risk. We just need them alive.
+ components:
+ - type: Objective
+ difficulty: 1.75
+ - type: TargetObjective
+ title: objective-condition-other-traitor-alive-title
+ - type: RandomTraitorAlive
+
+- type: entity
+ noSpawn: true
+ parent: [BaseTraitorSocialObjective, BaseHelpProgressObjective]
+ id: RandomTraitorProgressObjective
+ description: Identify yourself at your own risk. We just need them to succeed.
+ components:
+ - type: Objective
+ difficulty: 2.5
+ - type: TargetObjective
+ title: objective-condition-other-traitor-progress-title
+ - type: RandomTraitorProgress
+
+# steal
+
+## cmo
+
+- type: entity
+ noSpawn: true
+ parent: BaseTraitorStealObjective
+ id: HyposprayStealObjective
+ components:
+ - type: NotJobRequirement
+ job: ChiefMedicalOfficer
+ - type: StealCondition
+ prototype: Hypospray
+ owner: job-name-cmo
+
+## rd
+
+- type: entity
+ abstract: true
+ parent: BaseTraitorStealObjective
+ id: BaseRDObjective
+ components:
+ - type: NotJobRequirement
+ job: ResearchDirector
+ - type: StealCondition
+ owner: job-name-rd
+
+- type: entity
+ noSpawn: true
+ parent: BaseRDObjective
+ id: RDHardsuitStealObjective
+ components:
+ - type: StealCondition
+ prototype: ClothingOuterHardsuitRd
+
+- type: entity
+ noSpawn: true
+ parent: BaseRDObjective
+ id: HandTeleporterStealObjective
+ components:
+ - type: StealCondition
+ prototype: HandTeleporter
+
+## hos
+
+- type: entity
+ noSpawn: true
+ parent: BaseTraitorStealObjective
+ id: SecretDocumentsStealObjective
+ components:
+ - type: Objective
+ # hos has a gun ce does not, higher difficulty than most
+ difficulty: 3.5
+ - type: NotJobRequirement
+ job: HeadOfSecurity
+ - type: StealCondition
+ prototype: BookSecretDocuments
+ owner: job-name-hos
+
+## ce
+
+- type: entity
+ noSpawn: true
+ parent: BaseTraitorStealObjective
+ id: MagbootsStealObjective
+ components:
+ - type: NotJobRequirement
+ job: ChiefEngineer
+ - type: StealCondition
+ prototype: ClothingShoesBootsMagAdv
+ owner: job-name-ce
+
+## hop
+
+- type: entity
+ noSpawn: true
+ parent: BaseTraitorStealObjective
+ id: CorgiMeatStealObjective
+ components:
+ - type: NotJobRequirement
+ job: HeadOfPersonnel
+ - type: StealCondition
+ prototype: FoodMeatCorgi
+ owner: objective-condition-steal-Ian
+
+## cap
+
+- type: entity
+ abstract: true
+ parent: BaseTraitorStealObjective
+ id: BaseCaptainObjective
+ components:
+ - type: Objective
+ # sorry ce but your jordans are not as high security as the caps gear
+ difficulty: 3.5
+ - type: NotJobRequirement
+ job: Captain
+
+- type: entity
+ noSpawn: true
+ parent: BaseCaptainObjective
+ id: CaptainIDStealObjective
+ components:
+ - type: StealCondition
+ prototype: CaptainIDCard
+
+- type: entity
+ noSpawn: true
+ parent: BaseCaptainObjective
+ id: CaptainJetpackStealObjective
+ components:
+ - type: StealCondition
+ prototype: JetpackCaptainFilled
+
+- type: entity
+ noSpawn: true
+ parent: BaseCaptainObjective
+ id: CaptainGunStealObjective
+ components:
+ - type: StealCondition
+ prototype: WeaponAntiqueLaser
+ owner: job-name-captain
+
+- type: entity
+ noSpawn: true
+ parent: BaseCaptainObjective
+ id: StealNukeDiskObjective
+ components:
+ - type: Objective
+ # high difficulty since the hardest item both to steal, and to not get caught down the road,
+ # since anyone with a pinpointer can track you down and kill you
+ # it's close to being a stealth loneop
+ difficulty: 4.5
+ - type: NotCommandRequirement
+ - type: StealCondition
+ prototype: NukeDisk
+ owner: objective-condition-steal-station
+++ /dev/null
-- type: objective
- id: CaptainIDStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: Captain
- conditions:
- - !type:StealCondition
- prototype: CaptainIDCard
-
-- type: objective
- id: KillRandomObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - RandomTraitorAliveCondition
- conditions:
- - !type:KillRandomPersonCondition {}
- canBeDuplicate: true
-
-# technically its still possible for KillRandomObjective to roll a head but this is guaranteed, so higher difficulty
-# this also will not count missing evac as killing as heads are higher profile, so you really need to do the dirty work
-- type: objective
- id: KillRandomHeadObjective
- issuer: syndicate
- difficultyOverride: 3.0
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - RandomTraitorAliveCondition
- conditions:
- - !type:KillRandomHeadCondition {}
- # killing 1 head is enough
- canBeDuplicate: false
-
-- type: objective
- id: RandomTraitorAliveObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - KillRandomPersonCondition
- - !type:MultipleTraitorsRequirement
- conditions:
- - !type:RandomTraitorAliveCondition {}
- canBeDuplicate: true
-
-- type: objective
- id: DieObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - StealCondition
- - EscapeShuttleCondition
- conditions:
- - !type:DieCondition {}
-
-- type: objective
- id: CMOHyposprayStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: ChiefMedicalOfficer
- conditions:
- - !type:StealCondition
- prototype: Hypospray
- owner: job-name-cmo
-
-- type: objective
- id: RDHardsuitStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: ResearchDirector
- conditions:
- - !type:StealCondition
- prototype: ClothingOuterHardsuitRd
- owner: job-name-rd
-
-- type: objective
- id: HandTeleporterStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: ResearchDirector
- conditions:
- - !type:StealCondition
- prototype: HandTeleporter
- owner: job-name-rd
-
-- type: objective
- id: SecretDocumentsStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: HeadOfSecurity
- conditions:
- - !type:StealCondition
- prototype: BookSecretDocuments
- owner: job-name-hos
-
-- type: objective
- id: NukeDiskStealObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: Captain
- - !type:NotRoleRequirement
- roleId: HeadOfSecurity
- - !type:NotRoleRequirement
- roleId: HeadOfPersonnel
- - !type:NotRoleRequirement
- roleId: ChiefEngineer
- - !type:NotRoleRequirement
- roleId: ChiefMedicalOfficer
- - !type:NotRoleRequirement
- roleId: ResearchDirector
- conditions:
- - !type:StealCondition
- prototype: NukeDisk
- owner: objective-condition-steal-station
-
-- type: objective
- id: MagbootsStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: ChiefEngineer
- conditions:
- - !type:StealCondition
- prototype: ClothingShoesBootsMagAdv
- owner: job-name-ce
-
-- type: objective
- id: CorgiMeatStealObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: HeadOfPersonnel
- conditions:
- - !type:StealCondition
- prototype: FoodMeatCorgi
- owner: objective-condition-steal-Ian
-
-- type: objective
- id: CaptainGunStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: Captain
- conditions:
- - !type:StealCondition
- prototype: WeaponAntiqueLaser
- owner: job-name-captain
-
-- type: objective
- id: CaptainJetpackStealObjective
- issuer: syndicate
- difficultyOverride: 2.75
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- - !type:NotRoleRequirement
- roleId: Captain
- conditions:
- - !type:StealCondition
- prototype: JetpackCaptainFilled
-
-- type: objective
- id: EscapeShuttleObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:IncompatibleConditionsRequirement
- conditions:
- - DieCondition
- conditions:
- - !type:EscapeShuttleCondition {}
-
-- type: objective
- id: RandomTraitorProgressObjective
- issuer: syndicate
- requirements:
- - !type:TraitorRequirement {}
- - !type:MultipleTraitorsRequirement
- conditions:
- - !type:RandomTraitorProgressCondition {}
- canBeDuplicate: true