Objectives ecs rework (#19967)

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas
2023-09-16 07:18:10 +01:00
committed by GitHub
parent e8c58d1574
commit f7711edbe3
106 changed files with 2121 additions and 1779 deletions

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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));
}
}