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(
|
||||
EntityUid Entity,
|
||||
string Job,
|
||||
Dictionary<string, List<ConditionInfo>> Objectives,
|
||||
Dictionary<string, List<ObjectiveInfo>> Objectives,
|
||||
string? Briefing,
|
||||
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)
|
||||
{
|
||||
var conditionControl = new ObjectiveConditionsControl();
|
||||
conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.SpriteSpecifier);
|
||||
conditionControl.ProgressTexture.Texture = _sprite.Frame0(condition.Icon);
|
||||
conditionControl.ProgressTexture.Progress = condition.Progress;
|
||||
var titleMessage = new FormattedMessage();
|
||||
var descriptionMessage = new FormattedMessage();
|
||||
|
||||
@@ -3,6 +3,8 @@ using Content.Server.Roles;
|
||||
using Content.Server.Roles.Jobs;
|
||||
using Content.Shared.CharacterInfo;
|
||||
using Content.Shared.Objectives;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.CharacterInfo;
|
||||
|
||||
@@ -11,6 +13,7 @@ public sealed class CharacterInfoSystem : EntitySystem
|
||||
[Dependency] private readonly JobSystem _jobs = default!;
|
||||
[Dependency] private readonly MindSystem _minds = default!;
|
||||
[Dependency] private readonly RoleSystem _roles = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -27,7 +30,7 @@ public sealed class CharacterInfoSystem : EntitySystem
|
||||
|
||||
var entity = args.SenderSession.AttachedEntity.Value;
|
||||
|
||||
var conditions = new Dictionary<string, List<ConditionInfo>>();
|
||||
var objectives = new Dictionary<string, List<ObjectiveInfo>>();
|
||||
var jobTitle = "No Profession";
|
||||
string? briefing = null;
|
||||
if (_minds.TryGetMind(entity, out var mindId, out var mind))
|
||||
@@ -35,13 +38,15 @@ public sealed class CharacterInfoSystem : EntitySystem
|
||||
// Get objectives
|
||||
foreach (var objective in mind.AllObjectives)
|
||||
{
|
||||
if (!conditions.ContainsKey(objective.Prototype.Issuer))
|
||||
conditions[objective.Prototype.Issuer] = new List<ConditionInfo>();
|
||||
foreach (var condition in objective.Conditions)
|
||||
{
|
||||
conditions[objective.Prototype.Issuer].Add(new ConditionInfo(condition.Title,
|
||||
condition.Description, condition.Icon, condition.Progress));
|
||||
}
|
||||
var info = _objectives.GetInfo(objective, mindId, mind);
|
||||
if (info == null)
|
||||
continue;
|
||||
|
||||
// group objectives by their issuer
|
||||
var issuer = Comp<ObjectiveComponent>(objective).Issuer;
|
||||
if (!objectives.ContainsKey(issuer))
|
||||
objectives[issuer] = new List<ObjectiveInfo>();
|
||||
objectives[issuer].Add(info.Value);
|
||||
}
|
||||
|
||||
if (_jobs.MindTryGetJobName(mindId, out var jobName))
|
||||
@@ -51,6 +56,6 @@ public sealed class CharacterInfoSystem : EntitySystem
|
||||
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.Shared.Communications;
|
||||
using Content.Shared.Objectives;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
@@ -18,9 +17,9 @@ public sealed partial class NinjaRuleComponent : Component
|
||||
public List<EntityUid> Minds = new();
|
||||
|
||||
/// <summary>
|
||||
/// List of objective prototype ids to add
|
||||
/// List of objective entity prototypes to add
|
||||
/// </summary>
|
||||
[DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<ObjectivePrototype>))]
|
||||
[DataField("objectives", required: true, customTypeSerializer: typeof(PrototypeIdListSerializer<EntityPrototype>))]
|
||||
public List<string> Objectives = new();
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Preferences;
|
||||
using Content.Shared.Roles;
|
||||
@@ -299,8 +300,8 @@ public sealed class TraitorRuleSystem : GameRuleSystem<TraitorRuleComponent>
|
||||
if (objective == null)
|
||||
continue;
|
||||
|
||||
if (_mindSystem.TryAddObjective(mindId, mind, objective))
|
||||
difficulty += objective.Difficulty;
|
||||
_mindSystem.AddObjective(mindId, mind, objective.Value);
|
||||
difficulty += Comp<ObjectiveComponent>(objective.Value).Difficulty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ public sealed class SpaceNinjaSystem : SharedSpaceNinjaSystem
|
||||
// assign objectives - must happen after spider charge target so that the obj requirement works
|
||||
foreach (var objective in config.Objectives)
|
||||
{
|
||||
if (!_mind.TryAddObjective(mindId, objective, mind))
|
||||
if (!_mind.TryAddObjective(mindId, mind, objective))
|
||||
{
|
||||
Log.Error($"Failed to add {objective} to ninja {mind.OwnedEntity.Value}");
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -39,16 +39,17 @@ namespace Content.Server.Objectives.Commands
|
||||
}
|
||||
|
||||
if (!IoCManager.Resolve<IPrototypeManager>()
|
||||
.TryIndex<ObjectivePrototype>(args[1], out var objectivePrototype))
|
||||
.TryIndex<EntityPrototype>(args[1], out var proto) ||
|
||||
!proto.TryGetComponent<ObjectiveComponent>(out _))
|
||||
{
|
||||
shell.WriteLine($"Can't find matching ObjectivePrototype {objectivePrototype}");
|
||||
shell.WriteLine($"Can't find matching objective prototype {args[1]}");
|
||||
return;
|
||||
}
|
||||
|
||||
var mindSystem = _entityManager.System<SharedMindSystem>();
|
||||
if (!mindSystem.TryAddObjective(mindId, mind, objectivePrototype))
|
||||
if (!minds.TryAddObjective(mindId, mind, args[1]))
|
||||
{
|
||||
shell.WriteLine("Objective requirements dont allow that objective to be added.");
|
||||
// can fail for other reasons so dont pretend to be right
|
||||
shell.WriteLine("Failed to add the objective. Maybe requirements dont allow that objective to be added.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ using System.Linq;
|
||||
using Content.Server.Administration;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Console;
|
||||
|
||||
@@ -25,7 +26,7 @@ namespace Content.Server.Objectives.Commands
|
||||
}
|
||||
|
||||
var minds = _entities.System<SharedMindSystem>();
|
||||
if (!minds.TryGetMind(player, out _, out var mind))
|
||||
if (!minds.TryGetMind(player, out var mindId, out var mind))
|
||||
{
|
||||
shell.WriteError(LocalizationManager.GetString("shell-target-entity-does-not-have-message", ("missing", "mind")));
|
||||
return;
|
||||
@@ -38,9 +39,20 @@ namespace Content.Server.Objectives.Commands
|
||||
shell.WriteLine("None.");
|
||||
}
|
||||
|
||||
var objectivesSystem = _entities.System<SharedObjectivesSystem>();
|
||||
for (var i = 0; i < objectives.Count; i++)
|
||||
{
|
||||
shell.WriteLine($"- [{i}] {objectives[i].Conditions[0].Title}");
|
||||
var info = objectivesSystem.GetInfo(objectives[i], mindId, mind);
|
||||
if (info == null)
|
||||
{
|
||||
shell.WriteLine($"- [{i}] {objectives[i]} - INVALID");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var progress = (int) (info.Value.Progress * 100f);
|
||||
shell.WriteLine($"- [{i}] {objectives[i]} ({info.Value.Title}) ({progress}%)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace Content.Server.Objectives.Commands
|
||||
return;
|
||||
}
|
||||
|
||||
if (!minds.TryGetMind(session, out _, out var mind))
|
||||
if (!minds.TryGetMind(session, out var mindId, out var mind))
|
||||
{
|
||||
shell.WriteLine("Can't find the mind.");
|
||||
return;
|
||||
@@ -39,7 +39,7 @@ namespace Content.Server.Objectives.Commands
|
||||
if (int.TryParse(args[1], out var i))
|
||||
{
|
||||
var mindSystem = _entityManager.System<SharedMindSystem>();
|
||||
shell.WriteLine(mindSystem.TryRemoveObjective(mind, i)
|
||||
shell.WriteLine(mindSystem.TryRemoveObjective(mindId, mind, i)
|
||||
? "Objective successfully removed!"
|
||||
: "Objective removing failed. Maybe the index is out of bounds? Check lsobjectives!");
|
||||
}
|
||||
|
||||
@@ -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.Mind;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Random;
|
||||
using Content.Shared.Random.Helpers;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -11,7 +12,7 @@ using System.Linq;
|
||||
|
||||
namespace Content.Server.Objectives;
|
||||
|
||||
public sealed class ObjectivesSystem : EntitySystem
|
||||
public sealed class ObjectivesSystem : SharedObjectivesSystem
|
||||
{
|
||||
[Dependency] private readonly GameTicker _gameTicker = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
@@ -30,6 +31,8 @@ public sealed class ObjectivesSystem : EntitySystem
|
||||
/// </summary>
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
// go through each gamerule getting data for the roundend summary.
|
||||
var summaries = new Dictionary<string, Dictionary<string, List<EntityUid>>>();
|
||||
var query = EntityQueryEnumerator<GameRuleComponent>();
|
||||
while (query.MoveNext(out var uid, out var gameRule))
|
||||
{
|
||||
@@ -41,90 +44,133 @@ public sealed class ObjectivesSystem : EntitySystem
|
||||
if (info.Minds.Count == 0)
|
||||
continue;
|
||||
|
||||
// first group the gamerules by their agents, for example 2 different dragons
|
||||
var agent = info.AgentName;
|
||||
var result = Loc.GetString("objectives-round-end-result", ("count", info.Minds.Count), ("agent", agent));
|
||||
var prepend = new ObjectivesTextPrependEvent(result);
|
||||
if (!summaries.ContainsKey(agent))
|
||||
summaries[agent] = new Dictionary<string, List<EntityUid>>();
|
||||
|
||||
var prepend = new ObjectivesTextPrependEvent("");
|
||||
RaiseLocalEvent(uid, ref prepend);
|
||||
// space between the start text and player list
|
||||
result = prepend.Text + "\n";
|
||||
|
||||
foreach (var mindId in info.Minds)
|
||||
// next group them by their prepended texts
|
||||
// for example with traitor rule, group them by the codewords they share
|
||||
var summary = summaries[agent];
|
||||
if (summary.ContainsKey(prepend.Text))
|
||||
{
|
||||
if (!TryComp(mindId, out MindComponent? mind))
|
||||
continue;
|
||||
// same prepended text (usually empty) so combine them
|
||||
summary[prepend.Text].AddRange(info.Minds);
|
||||
}
|
||||
else
|
||||
{
|
||||
summary[prepend.Text] = info.Minds;
|
||||
}
|
||||
}
|
||||
|
||||
var name = mind.CharacterName;
|
||||
_mind.TryGetSession(mindId, out var session);
|
||||
var username = session?.Name;
|
||||
// convert the data into summary text
|
||||
foreach (var (agent, summary) in summaries)
|
||||
{
|
||||
// first get the total number of players that were in these game rules combined
|
||||
var total = 0;
|
||||
foreach (var (_, minds) in summary)
|
||||
{
|
||||
total += minds.Count;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
var result = Loc.GetString("objectives-round-end-result", ("count", total), ("agent", agent));
|
||||
// next add all the players with its own prepended text
|
||||
foreach (var (prepend, minds) in summary)
|
||||
{
|
||||
if (prepend != string.Empty)
|
||||
result += prepend;
|
||||
|
||||
// add space between the start text and player list
|
||||
result += "\n";
|
||||
|
||||
var objectives = mind.AllObjectives.ToArray();
|
||||
if (objectives.Length == 0)
|
||||
{
|
||||
result += Loc.GetString("objectives-no-objectives", ("title", title), ("agent", agent));
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AddSummary(ref result, agent, minds);
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -137,15 +183,16 @@ public sealed class ObjectivesSystem : EntitySystem
|
||||
|
||||
if (!_prototypeManager.TryIndex<WeightedRandomPrototype>(groupName, out var group))
|
||||
{
|
||||
Log.Error("Couldn't index objective group prototype" + groupName);
|
||||
Log.Error($"Couldn't index objective group prototype {groupName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_prototypeManager.TryIndex<ObjectivePrototype>(group.Pick(_random), out var objective)
|
||||
&& objective.CanBeAssigned(mindId, mind))
|
||||
var proto = group.Pick(_random);
|
||||
var objective = TryCreateObjective(mindId, mind, proto);
|
||||
if (objective != null)
|
||||
return objective;
|
||||
else
|
||||
tries++;
|
||||
|
||||
tries++;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -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 string JobTitle;
|
||||
public readonly Dictionary<string, List<ConditionInfo>> Objectives;
|
||||
public readonly Dictionary<string, List<ObjectiveInfo>> Objectives;
|
||||
public readonly string? Briefing;
|
||||
|
||||
public CharacterInfoEvent(NetEntity netEntity, string jobTitle, Dictionary<string, List<ConditionInfo>> objectives, string? briefing)
|
||||
public CharacterInfoEvent(NetEntity netEntity, string jobTitle, Dictionary<string, List<ObjectiveInfo>> objectives, string? briefing)
|
||||
{
|
||||
NetEntity = netEntity;
|
||||
JobTitle = jobTitle;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Objectives;
|
||||
using Robust.Shared.Network;
|
||||
using Robust.Shared.Players;
|
||||
|
||||
@@ -22,7 +21,7 @@ namespace Content.Shared.Mind
|
||||
[RegisterComponent]
|
||||
public sealed partial class MindComponent : Component
|
||||
{
|
||||
internal readonly List<Objective> Objectives = new();
|
||||
internal readonly List<EntityUid> Objectives = new();
|
||||
|
||||
/// <summary>
|
||||
/// The session ID of the player owning this mind.
|
||||
@@ -78,10 +77,10 @@ namespace Content.Shared.Mind
|
||||
|
||||
// TODO move objectives out of mind component
|
||||
/// <summary>
|
||||
/// An enumerable over all the objectives this mind has.
|
||||
/// An enumerable over all the objective entities this mind has.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public IEnumerable<Objective> AllObjectives => Objectives;
|
||||
public IEnumerable<EntityUid> AllObjectives => Objectives;
|
||||
|
||||
/// <summary>
|
||||
/// Prevents user from ghosting out
|
||||
|
||||
@@ -3,11 +3,13 @@ using Content.Shared.Administration.Logs;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Humanoid;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Mind.Components;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.Objectives;
|
||||
using Content.Shared.Objectives.Systems;
|
||||
using Content.Shared.Players;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Network;
|
||||
@@ -20,9 +22,9 @@ namespace Content.Shared.Mind;
|
||||
public abstract class SharedMindSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IPrototypeManager _proto = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
||||
[Dependency] private readonly SharedPlayerSystem _playerSystem = default!;
|
||||
[Dependency] private readonly MobStateSystem _mobState = default!;
|
||||
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;
|
||||
[Dependency] private readonly SharedPlayerSystem _player = default!;
|
||||
|
||||
// This is dictionary is required to track the minds of disconnected players that may have had their entity deleted.
|
||||
protected readonly Dictionary<NetUserId, EntityUid> UserMinds = new();
|
||||
@@ -90,7 +92,7 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
if (!mindContainer.ShowExamineInfo || !args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
var dead = _mobStateSystem.IsDead(uid);
|
||||
var dead = _mobState.IsDead(uid);
|
||||
var hasSession = CompOrNull<MindComponent>(mindContainer.Mind)?.Session;
|
||||
|
||||
if (dead && !mindContainer.HasMind)
|
||||
@@ -166,7 +168,7 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
if (targetMobState == null)
|
||||
return true;
|
||||
// They might actually be alive.
|
||||
return _mobStateSystem.IsDead(mind.OwnedEntity.Value, targetMobState);
|
||||
return _mobState.IsDead(mind.OwnedEntity.Value, targetMobState);
|
||||
}
|
||||
|
||||
public virtual void Visit(EntityUid mindId, EntityUid entity, MindComponent? mind = null)
|
||||
@@ -215,7 +217,7 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
|
||||
public void WipeMind(ICommonSession player)
|
||||
{
|
||||
var mind = _playerSystem.ContentData(player)?.Mind;
|
||||
var mind = _player.ContentData(player)?.Mind;
|
||||
DebugTools.Assert(GetMind(player.UserId) == mind);
|
||||
WipeMind(mind);
|
||||
}
|
||||
@@ -251,59 +253,44 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an objective to this mind.
|
||||
/// Tries to create and add an objective from its prototype id.
|
||||
/// </summary>
|
||||
public bool TryAddObjective(EntityUid mindId, MindComponent mind, ObjectivePrototype objectivePrototype)
|
||||
/// <returns>Returns true if adding the objective succeeded.</returns>
|
||||
public bool TryAddObjective(EntityUid mindId, MindComponent mind, string proto)
|
||||
{
|
||||
if (!objectivePrototype.CanBeAssigned(mindId, mind))
|
||||
return false;
|
||||
var objective = objectivePrototype.GetObjective(mindId, mind);
|
||||
if (mind.Objectives.Contains(objective))
|
||||
var objective = _objectives.TryCreateObjective(mindId, mind, proto);
|
||||
if (objective == null)
|
||||
return false;
|
||||
|
||||
foreach (var condition in objective.Conditions)
|
||||
{
|
||||
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' added to mind of {MindOwnerLoggingString(mind)}");
|
||||
}
|
||||
|
||||
mind.Objectives.Add(objective);
|
||||
AddObjective(mindId, mind, objective.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an objective, by id, to this mind.
|
||||
/// Adds an objective that already exists, and is assumed to have had its requirements checked.
|
||||
/// </summary>
|
||||
public bool TryAddObjective(EntityUid mindId, string name, MindComponent? mind = null)
|
||||
public void AddObjective(EntityUid mindId, MindComponent mind, EntityUid objective)
|
||||
{
|
||||
if (!Resolve(mindId, ref mind))
|
||||
return false;
|
||||
|
||||
if (!_proto.TryIndex<ObjectivePrototype>(name, out var objective))
|
||||
{
|
||||
Log.Error($"Tried to add unknown objective prototype: {name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryAddObjective(mindId, mind, objective);
|
||||
var title = Name(objective);
|
||||
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) added to mind of {MindOwnerLoggingString(mind)}");
|
||||
mind.Objectives.Add(objective);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an objective to this mind.
|
||||
/// Removes an objective from this mind.
|
||||
/// </summary>
|
||||
/// <returns>Returns true if the removal succeeded.</returns>
|
||||
public bool TryRemoveObjective(MindComponent mind, int index)
|
||||
public bool TryRemoveObjective(EntityUid mindId, MindComponent mind, int index)
|
||||
{
|
||||
if (index < 0 || index >= mind.Objectives.Count)
|
||||
return false;
|
||||
|
||||
var objective = mind.Objectives[index];
|
||||
|
||||
foreach (var condition in objective.Conditions)
|
||||
{
|
||||
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"'{condition.Title}' removed from the mind of {MindOwnerLoggingString(mind)}");
|
||||
}
|
||||
|
||||
var title = Name(objective);
|
||||
_adminLogger.Add(LogType.Mind, LogImpact.Low, $"Objective {objective} ({title}) removed from the mind of {MindOwnerLoggingString(mind)}");
|
||||
mind.Objectives.Remove(objective);
|
||||
Del(objective);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -356,7 +343,7 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
{
|
||||
mindId = default;
|
||||
mind = null;
|
||||
return _playerSystem.ContentData(player) is { } data && TryGetMind(data, out mindId, out mind);
|
||||
return _player.ContentData(player) is { } data && TryGetMind(data, out mindId, out mind);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -432,6 +419,30 @@ public abstract class SharedMindSystem : EntitySystem
|
||||
{
|
||||
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>
|
||||
|
||||
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-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-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-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-with-objectives = {$title} was a {$agent} who had the following objectives:
|
||||
|
||||
objectives-condition-success = {$condition} | [color={$markupColor}]Success![/color]
|
||||
objectives-condition-fail = {$condition} | [color={$markupColor}]Failure![/color] ({$progress}%)
|
||||
objectives-objective-success = {$objective} | [color={$markupColor}]Success![/color]
|
||||
objectives-objective-fail = {$objective} | [color={$markupColor}]Failure![/color] ({$progress}%)
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
- DoorjackObjective
|
||||
- SpiderChargeObjective
|
||||
- TerrorObjective
|
||||
- SurviveObjective
|
||||
- NinjaSurviveObjective
|
||||
threats:
|
||||
- announcement: terror-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