Objectives ecs rework (#19967)
Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
@@ -59,7 +59,7 @@ public sealed class CharacterInfoSystem : EntitySystem
|
|||||||
public readonly record struct CharacterData(
|
public readonly record struct CharacterData(
|
||||||
EntityUid Entity,
|
EntityUid Entity,
|
||||||
string Job,
|
string Job,
|
||||||
Dictionary<string, List<ConditionInfo>> Objectives,
|
Dictionary<string, List<ObjectiveInfo>> Objectives,
|
||||||
string? Briefing,
|
string? Briefing,
|
||||||
string EntityName
|
string EntityName
|
||||||
);
|
);
|
||||||
|
|||||||
7
Content.Client/Objectives/Systems/ObjectivesSystem.cs
Normal file
7
Content.Client/Objectives/Systems/ObjectivesSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
using Content.Shared.Objectives.Systems;
|
||||||
|
|
||||||
|
namespace Content.Client.Objectives.Systems;
|
||||||
|
|
||||||
|
public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -128,7 +128,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
|||||||
foreach (var condition in conditions)
|
foreach (var condition in conditions)
|
||||||
{
|
{
|
||||||
var conditionControl = new ObjectiveConditionsControl();
|
var conditionControl = new ObjectiveConditionsControl();
|
||||||
conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.SpriteSpecifier);
|
conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.Icon);
|
||||||
conditionControl.ProgressTexture.Progress = condition.Progress;
|
conditionControl.ProgressTexture.Progress = condition.Progress;
|
||||||
var titleMessage = new FormattedMessage();
|
var titleMessage = new FormattedMessage();
|
||||||
var descriptionMessage = new FormattedMessage();
|
var descriptionMessage = new FormattedMessage();
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ using Content.Server.Roles;
|
|||||||
using Content.Server.Roles.Jobs;
|
using Content.Server.Roles.Jobs;
|
||||||
using Content.Shared.CharacterInfo;
|
using Content.Shared.CharacterInfo;
|
||||||
using Content.Shared.Objectives;
|
using Content.Shared.Objectives;
|
||||||
|
using Content.Shared.Objectives.Components;
|
||||||
|
using Content.Shared.Objectives.Systems;
|
||||||
|
|
||||||
namespace Content.Server.CharacterInfo;
|
namespace Content.Server.CharacterInfo;
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ public sealed class CharacterInfoSystem : EntitySystem
|
|||||||
[Dependency] private readonly JobSystem _jobs = default!;
|
[Dependency] private readonly JobSystem _jobs = default!;
|
||||||
[Dependency] private readonly MindSystem _minds = default!;
|
[Dependency] private readonly MindSystem _minds = default!;
|
||||||
[Dependency] private readonly RoleSystem _roles = default!;
|
[Dependency] private readonly RoleSystem _roles = default!;
|
||||||
|
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -27,7 +30,7 @@ public sealed class CharacterInfoSystem : EntitySystem
|
|||||||
|
|
||||||
var entity = args.SenderSession.AttachedEntity.Value;
|
var entity = args.SenderSession.AttachedEntity.Value;
|
||||||
|
|
||||||
var conditions = new Dictionary<string, List<ConditionInfo>>();
|
var objectives = new Dictionary<string, List<ObjectiveInfo>>();
|
||||||
var jobTitle = "No Profession";
|
var jobTitle = "No Profession";
|
||||||
string? briefing = null;
|
string? briefing = null;
|
||||||
if (_minds.TryGetMind(entity, out var mindId, out var mind))
|
if (_minds.TryGetMind(entity, out var mindId, out var mind))
|
||||||
@@ -35,13 +38,15 @@ public sealed class CharacterInfoSystem : EntitySystem
|
|||||||
// Get objectives
|
// Get objectives
|
||||||
foreach (var objective in mind.AllObjectives)
|
foreach (var objective in mind.AllObjectives)
|
||||||
{
|
{
|
||||||
if (!conditions.ContainsKey(objective.Prototype.Issuer))
|
var info = _objectives.GetInfo(objective, mindId, mind);
|
||||||
conditions[objective.Prototype.Issuer] = new List<ConditionInfo>();
|
if (info == null)
|
||||||
foreach (var condition in objective.Conditions)
|
continue;
|
||||||
{
|
|
||||||
conditions[objective.Prototype.Issuer].Add(new ConditionInfo(condition.Title,
|
// group objectives by their issuer
|
||||||
condition.Description, condition.Icon, condition.Progress));
|
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))
|
if (_jobs.MindTryGetJobName(mindId, out var jobName))
|
||||||
@@ -51,6 +56,6 @@ public sealed class CharacterInfoSystem : EntitySystem
|
|||||||
briefing = _roles.MindGetBriefing(mindId);
|
briefing = _roles.MindGetBriefing(mindId);
|
||||||
}
|
}
|
||||||
|
|
||||||
RaiseNetworkEvent(new CharacterInfoEvent(GetNetEntity(entity), jobTitle, conditions, briefing), args.SenderSession);
|
RaiseNetworkEvent(new CharacterInfoEvent(GetNetEntity(entity), jobTitle, objectives, briefing), args.SenderSession);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Content.Server.Ninja.Systems;
|
using Content.Server.Ninja.Systems;
|
||||||
using Content.Shared.Communications;
|
using Content.Shared.Communications;
|
||||||
using Content.Shared.Objectives;
|
|
||||||
using Robust.Shared.Audio;
|
using Robust.Shared.Audio;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||||
@@ -18,9 +17,9 @@ public sealed partial class NinjaRuleComponent : Component
|
|||||||
public List<EntityUid> Minds = new();
|
public List<EntityUid> Minds = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// List of objective prototype ids to add
|
/// List of objective entity prototypes to add
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ObjectivePrototype>))]
|
[DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||||
public List<string> Objectives = new();
|
public List<string> Objectives = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
|
|||||||
using Content.Shared.Dataset;
|
using Content.Shared.Dataset;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
|
using Content.Shared.Objectives.Components;
|
||||||
using Content.Shared.PDA;
|
using Content.Shared.PDA;
|
||||||
using Content.Shared.Preferences;
|
using Content.Shared.Preferences;
|
||||||
using Content.Shared.Roles;
|
using Content.Shared.Roles;
|
||||||
@@ -299,8 +300,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
|||||||
if (objective == null)
|
if (objective == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (_mindSystem.TryAddObjective(mindId, mind, objective))
|
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
||||||
difficulty += objective.Difficulty;
|
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
|||||||
// assign objectives - must happen after spider charge target so that the obj requirement works
|
// assign objectives - must happen after spider charge target so that the obj requirement works
|
||||||
foreach (var objective in config.Objectives)
|
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}");
|
Log.Error($"Failed to add {objective} to ninja {mind.OwnedEntity.Value}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
using Content.Shared.Objectives;
|
using Content.Shared.Objectives.Components;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -39,16 +39,17 @@ namespace Content.Server.Objectives.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!IoCManager.Resolve<IPrototypeManager>()
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var mindSystem = _entityManager.System<SharedMindSystem>();
|
if (!minds.TryAddObjective(mindId, mind, args[1]))
|
||||||
if (!mindSystem.TryAddObjective(mindId, mind, objectivePrototype))
|
|
||||||
{
|
{
|
||||||
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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Linq;
|
|||||||
using Content.Server.Administration;
|
using Content.Server.Administration;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Mind;
|
using Content.Shared.Mind;
|
||||||
|
using Content.Shared.Objectives.Systems;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
@@ -25,7 +26,7 @@ namespace Content.Server.Objectives.Commands
|
|||||||
}
|
}
|
||||||
|
|
||||||
var minds = _entities.System<SharedMindSystem>();
|
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")));
|
shell.WriteError(LocalizationManager.GetString("shell-target-entity-does-not-have-message", ("missing", "mind")));
|
||||||
return;
|
return;
|
||||||
@@ -38,9 +39,20 @@ namespace Content.Server.Objectives.Commands
|
|||||||
shell.WriteLine("None.");
|
shell.WriteLine("None.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var objectivesSystem = _entities.System<SharedObjectivesSystem>();
|
||||||
for (var i = 0; i < objectives.Count; i++)
|
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}%)");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Content.Server.Objectives.Commands
|
|||||||
return;
|
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.");
|
shell.WriteLine("Can't find the mind.");
|
||||||
return;
|
return;
|
||||||
@@ -39,7 +39,7 @@ namespace Content.Server.Objectives.Commands
|
|||||||
if (int.TryParse(args[1], out var i))
|
if (int.TryParse(args[1], out var i))
|
||||||
{
|
{
|
||||||
var mindSystem = _entityManager.System<SharedMindSystem>();
|
var mindSystem = _entityManager.System<SharedMindSystem>();
|
||||||
shell.WriteLine(mindSystem.TryRemoveObjective(mind, i)
|
shell.WriteLine(mindSystem.TryRemoveObjective(mindId, mind, i)
|
||||||
? "Objective successfully removed!"
|
? "Objective successfully removed!"
|
||||||
: "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!");
|
: "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
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");
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Server.Mind;
|
using Content.Server.Mind;
|
||||||
using Content.Shared.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;
|
||||||
using Content.Shared.Random.Helpers;
|
using Content.Shared.Random.Helpers;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
@@ -11,7 +12,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Content.Server.Objectives;
|
namespace Content.Server.Objectives;
|
||||||
|
|
||||||
public sealed class ObjectivesSystem : EntitySystem
|
public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
@@ -30,6 +31,8 @@ public sealed class ObjectivesSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
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>();
|
var query = EntityQueryEnumerator<GameRuleComponent>();
|
||||||
while (query.MoveNext(out var uid, out var gameRule))
|
while (query.MoveNext(out var uid, out var gameRule))
|
||||||
{
|
{
|
||||||
@@ -41,90 +44,133 @@ public sealed class ObjectivesSystem : EntitySystem
|
|||||||
if (info.Minds.Count == 0)
|
if (info.Minds.Count == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// first group the gamerules by their agents, for example 2 different dragons
|
||||||
var agent = info.AgentName;
|
var agent = info.AgentName;
|
||||||
var result = Loc.GetString("objectives-round-end-result", ("count", info.Minds.Count), ("agent", agent));
|
if (!summaries.ContainsKey(agent))
|
||||||
var prepend = new ObjectivesTextPrependEvent(result);
|
summaries[agent] = new Dictionary<string, List<EntityUid>>();
|
||||||
|
|
||||||
|
var prepend = new ObjectivesTextPrependEvent("");
|
||||||
RaiseLocalEvent(uid, ref prepend);
|
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))
|
// same prepended text (usually empty) so combine them
|
||||||
continue;
|
summary[prepend.Text].AddRange(info.Minds);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
summary[prepend.Text] = info.Minds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var name = mind.CharacterName;
|
// convert the data into summary text
|
||||||
_mind.TryGetSession(mindId, out var session);
|
foreach (var (agent, summary) in summaries)
|
||||||
var username = session?.Name;
|
{
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
string title;
|
var result = Loc.GetString("objectives-round-end-result", ("count", total), ("agent", agent));
|
||||||
if (username != null)
|
// next add all the players with its own prepended text
|
||||||
{
|
foreach (var (prepend, minds) in summary)
|
||||||
if (name != null)
|
{
|
||||||
title = Loc.GetString("objectives-player-user-named", ("user", username), ("name", name));
|
if (prepend != string.Empty)
|
||||||
else
|
result += prepend;
|
||||||
title = Loc.GetString("objectives-player-user", ("user", username));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// nothing to identify the player by, just give up
|
|
||||||
if (name == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
title = Loc.GetString("objectives-player-named", ("name", name));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// add space between the start text and player list
|
||||||
result += "\n";
|
result += "\n";
|
||||||
|
|
||||||
var objectives = mind.AllObjectives.ToArray();
|
AddSummary(ref result, agent, minds);
|
||||||
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 => o.Prototype.Issuer))
|
|
||||||
{
|
|
||||||
result += "\n" + Loc.GetString($"objective-issuer-{objectiveGroup.Key}");
|
|
||||||
|
|
||||||
foreach (var objective in objectiveGroup)
|
|
||||||
{
|
|
||||||
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")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ev.AddLine(result + "\n");
|
ev.AddLine(result + "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ObjectivePrototype? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto)
|
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;
|
||||||
|
|
||||||
|
title = Loc.GetString("objectives-player-named", ("name", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
var info = GetInfo(objective, mindId, mind);
|
||||||
|
if (info == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var objectiveTitle = info.Value.Title;
|
||||||
|
var progress = info.Value.Progress;
|
||||||
|
if (progress > 0.99f)
|
||||||
|
{
|
||||||
|
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")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto)
|
||||||
{
|
{
|
||||||
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(objectiveGroupProto, out var groups))
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,15 +183,16 @@ public sealed class ObjectivesSystem : EntitySystem
|
|||||||
|
|
||||||
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(groupName, out var group))
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_prototypeManager.TryIndex<ObjectivePrototype>(group.Pick(_random), out var objective)
|
var proto = group.Pick(_random);
|
||||||
&& objective.CanBeAssigned(mindId, mind))
|
var objective = TryCreateObjective(mindId, mind, proto);
|
||||||
|
if (objective != null)
|
||||||
return objective;
|
return objective;
|
||||||
else
|
|
||||||
tries++;
|
tries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
22
Content.Server/Objectives/Systems/DieConditionSystem.cs
Normal file
22
Content.Server/Objectives/Systems/DieConditionSystem.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
111
Content.Server/Objectives/Systems/HelpProgressConditionSystem.cs
Normal file
111
Content.Server/Objectives/Systems/HelpProgressConditionSystem.cs
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
66
Content.Server/Objectives/Systems/KeepAliveCondition.cs
Normal file
66
Content.Server/Objectives/Systems/KeepAliveCondition.cs
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
131
Content.Server/Objectives/Systems/KillPersonConditionSystem.cs
Normal file
131
Content.Server/Objectives/Systems/KillPersonConditionSystem.cs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
106
Content.Server/Objectives/Systems/NinjaConditionsSystem.cs
Normal file
106
Content.Server/Objectives/Systems/NinjaConditionsSystem.cs
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Content.Server/Objectives/Systems/NotJobRequirementSystem.cs
Normal file
31
Content.Server/Objectives/Systems/NotJobRequirementSystem.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
Content.Server/Objectives/Systems/NumberObjectiveSystem.cs
Normal file
48
Content.Server/Objectives/Systems/NumberObjectiveSystem.cs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
Content.Server/Objectives/Systems/RoleRequirementSystem.cs
Normal file
28
Content.Server/Objectives/Systems/RoleRequirementSystem.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
93
Content.Server/Objectives/Systems/StealConditionSystem.cs
Normal file
93
Content.Server/Objectives/Systems/StealConditionSystem.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
25
Content.Server/Objectives/Systems/SurviveConditionSystem.cs
Normal file
25
Content.Server/Objectives/Systems/SurviveConditionSystem.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Content.Server/Objectives/Systems/TargetObjectiveSystem.cs
Normal file
68
Content.Server/Objectives/Systems/TargetObjectiveSystem.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,10 +19,10 @@ public sealed class CharacterInfoEvent : EntityEventArgs
|
|||||||
{
|
{
|
||||||
public readonly NetEntity NetEntity;
|
public readonly NetEntity NetEntity;
|
||||||
public readonly string JobTitle;
|
public readonly string JobTitle;
|
||||||
public readonly Dictionary<string, List<ConditionInfo>> Objectives;
|
public readonly Dictionary<string, List<ObjectiveInfo>> Objectives;
|
||||||
public readonly string? Briefing;
|
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;
|
NetEntity = netEntity;
|
||||||
JobTitle = jobTitle;
|
JobTitle = jobTitle;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
using Content.Shared.Mind.Components;
|
using Content.Shared.Mind.Components;
|
||||||
using Content.Shared.Objectives;
|
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
using Robust.Shared.Players;
|
using Robust.Shared.Players;
|
||||||
|
|
||||||
@@ -22,7 +21,7 @@ namespace Content.Shared.Mind
|
|||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed partial class MindComponent : Component
|
public sealed partial class MindComponent : Component
|
||||||
{
|
{
|
||||||
internal readonly List<Objective> Objectives = new();
|
internal readonly List<EntityUid> Objectives = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The session ID of the player owning this mind.
|
/// The session ID of the player owning this mind.
|
||||||
@@ -78,10 +77,10 @@ namespace Content.Shared.Mind
|
|||||||
|
|
||||||
// TODO move objectives out of mind component
|
// TODO move objectives out of mind component
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An enumerable over all the objectives this mind has.
|
/// An enumerable over all the objective entities this mind has.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
public IEnumerable<Objective> AllObjectives => Objectives;
|
public IEnumerable<EntityUid> AllObjectives => Objectives;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Prevents user from ghosting out
|
/// Prevents user from ghosting out
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ using Content.Shared.Administration.Logs;
|
|||||||
using Content.Shared.Database;
|
using Content.Shared.Database;
|
||||||
using Content.Shared.Examine;
|
using Content.Shared.Examine;
|
||||||
using Content.Shared.GameTicking;
|
using Content.Shared.GameTicking;
|
||||||
|
using Content.Shared.Humanoid;
|
||||||
using Content.Shared.Interaction.Events;
|
using Content.Shared.Interaction.Events;
|
||||||
using Content.Shared.Mind.Components;
|
using Content.Shared.Mind.Components;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Shared.Mobs.Components;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Shared.Mobs.Systems;
|
||||||
using Content.Shared.Objectives;
|
using Content.Shared.Objectives;
|
||||||
|
using Content.Shared.Objectives.Systems;
|
||||||
using Content.Shared.Players;
|
using Content.Shared.Players;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Network;
|
using Robust.Shared.Network;
|
||||||
@@ -20,9 +22,9 @@ namespace Content.Shared.Mind;
|
|||||||
public abstract class SharedMindSystem : EntitySystem
|
public abstract class SharedMindSystem : EntitySystem
|
||||||
{
|
{
|
||||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||||
[Dependency] private readonly SharedPlayerSystem _playerSystem = 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.
|
// 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();
|
protected readonly Dictionary<NetUserId, EntityUid> UserMinds = new();
|
||||||
@@ -90,7 +92,7 @@ public abstract class SharedMindSystem : EntitySystem
|
|||||||
if (!mindContainer.ShowExamineInfo || !args.IsInDetailsRange)
|
if (!mindContainer.ShowExamineInfo || !args.IsInDetailsRange)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var dead = _mobStateSystem.IsDead(uid);
|
var dead = _mobState.IsDead(uid);
|
||||||
var hasSession = CompOrNull<MindComponent>(mindContainer.Mind)?.Session;
|
var hasSession = CompOrNull<MindComponent>(mindContainer.Mind)?.Session;
|
||||||
|
|
||||||
if (dead && !mindContainer.HasMind)
|
if (dead && !mindContainer.HasMind)
|
||||||
@@ -166,7 +168,7 @@ public abstract class SharedMindSystem : EntitySystem
|
|||||||
if (targetMobState == null)
|
if (targetMobState == null)
|
||||||
return true;
|
return true;
|
||||||
// They might actually be alive.
|
// 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 virtual void Visit(EntityUid mindId, EntityUid entity, MindComponent? mind = null)
|
||||||
@@ -215,7 +217,7 @@ public abstract class SharedMindSystem : EntitySystem
|
|||||||
|
|
||||||
public void WipeMind(ICommonSession player)
|
public void WipeMind(ICommonSession player)
|
||||||
{
|
{
|
||||||
var mind = _playerSystem.ContentData(player)?.Mind;
|
var mind = _player.ContentData(player)?.Mind;
|
||||||
DebugTools.Assert(GetMind(player.UserId) == mind);
|
DebugTools.Assert(GetMind(player.UserId) == mind);
|
||||||
WipeMind(mind);
|
WipeMind(mind);
|
||||||
}
|
}
|
||||||
@@ -251,59 +253,44 @@ public abstract class SharedMindSystem : EntitySystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds an objective to this mind.
|
/// Tries to create and add an objective from its prototype id.
|
||||||
/// </summary>
|
/// </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);
|
||||||
return false;
|
if (objective == null)
|
||||||
var objective = objectivePrototype.GetObjective(mindId, mind);
|
|
||||||
if (mind.Objectives.Contains(objective))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
foreach (var condition in objective.Conditions)
|
AddObjective(mindId, mind, objective.Value);
|
||||||
{
|
|
||||||
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' added to mind of {MindOwnerLoggingString(mind)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
mind.Objectives.Add(objective);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </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))
|
var title = Name(objective);
|
||||||
return false;
|
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) added to mind of {MindOwnerLoggingString(mind)}");
|
||||||
|
mind.Objectives.Add(objective);
|
||||||
if (!_proto.TryIndex<ObjectivePrototype>(name, out var objective))
|
|
||||||
{
|
|
||||||
Log.Error($"Tried to add unknown objective prototype: {name}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TryAddObjective(mindId, mind, objective);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes an objective to this mind.
|
/// Removes an objective from this mind.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Returns true if the removal succeeded.</returns>
|
/// <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)
|
if (index < 0 || index >= mind.Objectives.Count)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var objective = mind.Objectives[index];
|
var objective = mind.Objectives[index];
|
||||||
|
|
||||||
foreach (var condition in objective.Conditions)
|
var title = Name(objective);
|
||||||
{
|
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) removed from the mind of {MindOwnerLoggingString(mind)}");
|
||||||
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' removed from the mind of {MindOwnerLoggingString(mind)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
mind.Objectives.Remove(objective);
|
mind.Objectives.Remove(objective);
|
||||||
|
Del(objective);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +343,7 @@ public abstract class SharedMindSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
mindId = default;
|
mindId = default;
|
||||||
mind = null;
|
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>
|
/// <summary>
|
||||||
@@ -432,6 +419,30 @@ public abstract class SharedMindSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
return TryGetMind(userId, out _, out var mind) ? mind.CharacterName : null;
|
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>
|
/// <summary>
|
||||||
|
|||||||
69
Content.Shared/Objectives/Components/ObjectiveComponent.cs
Normal file
69
Content.Shared/Objectives/Components/ObjectiveComponent.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
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);
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
Content.Shared/Objectives/ObjectiveInfo.cs
Normal file
17
Content.Shared/Objectives/ObjectiveInfo.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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);
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
130
Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
Normal file
130
Content.Shared/Objectives/Systems/SharedObjectivesSystem.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
objective-condition-die-title = Die a glorious death
|
|
||||||
objective-condition-die-description = Die.
|
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
objective-condition-doorjack-title = Doorjack {$count} doors on the station.
|
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.
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
objective-condition-kill-head-description = We need this head gone and you probably know why. Good luck, agent.
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
objective-condition-kill-head-title = Kill {$targetName}, {CAPITALIZE($job)}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
objective-condition-kill-person-title = Kill or maroon {$targetName}, {CAPITALIZE($job)}
|
||||||
@@ -1,2 +1 @@
|
|||||||
objective-condition-other-traitor-alive-title = Ensure fellow traitor {$targetName}, {CAPITALIZE($job)} stays alive.
|
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.
|
|
||||||
@@ -1,2 +1 @@
|
|||||||
objective-condition-other-traitor-progress-title = Ensure fellow traitor {$targetName}, {CAPITALIZE($job)} achieves at least half their objectives.
|
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.
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
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!
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
objective-condition-spider-charge-title = Detonate the spider clan charge in {$location}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
objective-condition-steal-research-title = Steal {$count} technologies.
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
objective-condition-survive-title = Survive
|
|
||||||
objective-condition-survive-description = You wouldn't be a very good ninja if you died, now would you?
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
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.
|
|
||||||
@@ -10,5 +10,5 @@ objectives-player-named = [color=White]{$name}[/color]
|
|||||||
objectives-no-objectives = {$title} was a {$agent}.
|
objectives-no-objectives = {$title} was a {$agent}.
|
||||||
objectives-with-objectives = {$title} was a {$agent} who had the following objectives:
|
objectives-with-objectives = {$title} was a {$agent} who had the following objectives:
|
||||||
|
|
||||||
objectives-condition-success = {$condition} | [color={$markupColor}]Success![/color]
|
objectives-objective-success = {$objective} | [color={$markupColor}]Success![/color]
|
||||||
objectives-condition-fail = {$condition} | [color={$markupColor}]Failure![/color] ({$progress}%)
|
objectives-objective-fail = {$objective} | [color={$markupColor}]Failure![/color] ({$progress}%)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
- DoorjackObjective
|
- DoorjackObjective
|
||||||
- SpiderChargeObjective
|
- SpiderChargeObjective
|
||||||
- TerrorObjective
|
- TerrorObjective
|
||||||
- SurviveObjective
|
- NinjaSurviveObjective
|
||||||
threats:
|
threats:
|
||||||
- announcement: terror-dragon
|
- announcement: terror-dragon
|
||||||
rule: Dragon
|
rule: Dragon
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user