diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 480c86b1c48..a1d48f571f0 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -19,6 +19,7 @@ using Robust.Shared.Utility; using Content.Shared.Roles.Components; using Content.Server._Scp.GameTicking.Rules.Components; // Fire edit +using Content.Shared.Mind; // Fire edit namespace Content.Server.Administration.Systems; @@ -284,6 +285,16 @@ private void AddAntagVerbs(GetVerbsEvent args) Act = () => { _antag.ForceMakeAntag(targetPlayer, DefaultScpSleepChaosSpyRule); + if (!_mindSystem.TryGetMind(args.Target, out var targetMindId, out _)) + return; + + if (!TryComp(targetMindId, out var targetMind)) + return; + + var targetName = targetMind.CharacterName ?? Name(args.Target); + _chatManager.DispatchServerMessage(actor.PlayerSession, + Loc.GetString("admin-verb-result-make-chaos-sleep-spy", + ("targetName", targetName))); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-chaos-sleep-spy"), diff --git a/Content.Server/_Scp/GameTicking/Rules/ChaosSleepSpyRuleSystem.cs b/Content.Server/_Scp/GameTicking/Rules/ChaosSleepSpyRuleSystem.cs index 7bf23b73cb3..232bd7d090a 100644 --- a/Content.Server/_Scp/GameTicking/Rules/ChaosSleepSpyRuleSystem.cs +++ b/Content.Server/_Scp/GameTicking/Rules/ChaosSleepSpyRuleSystem.cs @@ -5,9 +5,12 @@ using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.Mind; +using Content.Server.Objectives; using Content.Server.Roles.RoleCodeword; using Content.Shared._Scp.Chaos; using Content.Shared.GameTicking.Components; +using Content.Shared.Mind; +using Content.Shared.Mobs.Systems; using Content.Shared.Roles.RoleCodeword; using Content.Shared.Speech; using Content.Shared.Speech.Components; @@ -19,8 +22,10 @@ public sealed class ChaosSleepSpyRuleSystem : GameRuleSystem ent, ref Aft var sleepSpyMobComp = EnsureComp(args.EntityUid); sleepSpyMobComp.CodeWords = ent.Comp.CodeWords; + + + EntityUid? selectedMindId = null; + MindComponent? selectedMindComp = null; + int minObjectives = int.MaxValue; + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (!_mind.TryGetMind(uid, out var thisMindId, out var mindComp)) + continue; + + if(_mobState.IsDead(uid)) + continue; + + var count = mindComp.Objectives.Count; + if (count < minObjectives) + { + minObjectives = count; + selectedMindId = thisMindId; + selectedMindComp = mindComp; + } + } + + if (selectedMindId.HasValue && selectedMindComp != null) + { + if (_objectives.TryCreateObjective(selectedMindId.Value, selectedMindComp, "ChaosSpyActivateSleepSpyObjective") is not { } objectiveUid) + return; + + _mind.AddObjective(selectedMindId.Value, selectedMindComp, objectiveUid); + } } private void OnListen(Entity ent, ref ListenEvent args) @@ -82,6 +118,7 @@ private void TryUnsleepSpy(Entity ent) return; _antag.ForceMakeAntag(actorComp.PlayerSession, ent.Comp.DefaultChaosSpyRule); + EnsureComp(ent); RemCompDeferred(ent); RemCompDeferred(ent); } diff --git a/Content.Server/_Scp/GameTicking/Rules/ChaosSpyRuleSystem.cs b/Content.Server/_Scp/GameTicking/Rules/ChaosSpyRuleSystem.cs index e58bfbcf826..d0ccb8a71d1 100644 --- a/Content.Server/_Scp/GameTicking/Rules/ChaosSpyRuleSystem.cs +++ b/Content.Server/_Scp/GameTicking/Rules/ChaosSpyRuleSystem.cs @@ -174,7 +174,7 @@ private void OnObjectivesTextPrepend(Entity ent, ref Obje args.Text += "\n" + Loc.GetString("chaos-spy-round-end-codewords", ("codewords", string.Join(", ", ent.Comp.CodeWords))); } - private string GenerateBriefing(string[]? codewords, Note[]? uplinkCode,bool isSleepSpy , string? objectiveIssuer = null) + private string GenerateBriefing(string[]? codewords, Note[]? uplinkCode, bool isSleepSpy, string? objectiveIssuer = null) { var sb = new StringBuilder(); diff --git a/Content.Server/_Scp/Objectives/Components/ScpActivateSleepSpyConditionComponent.cs b/Content.Server/_Scp/Objectives/Components/ScpActivateSleepSpyConditionComponent.cs new file mode 100644 index 00000000000..6a3c67d05bd --- /dev/null +++ b/Content.Server/_Scp/Objectives/Components/ScpActivateSleepSpyConditionComponent.cs @@ -0,0 +1,11 @@ +using Content.Server._Scp.Objectives.Systems; + +namespace Content.Server._Scp.Objectives.Components; + +[RegisterComponent, Access(typeof(ScpActivateSleepSpyConditionSystem))] +public sealed partial class ScpActivateSleepSpyConditionComponent : Component +{ + /// The sleep spy target that an active Chaos Spy must activate. + [ViewVariables] + public EntityUid? Target; +} diff --git a/Content.Server/_Scp/Objectives/Systems/ScpActivateSleepSpyConditionSystem.cs b/Content.Server/_Scp/Objectives/Systems/ScpActivateSleepSpyConditionSystem.cs new file mode 100644 index 00000000000..e31b797795e --- /dev/null +++ b/Content.Server/_Scp/Objectives/Systems/ScpActivateSleepSpyConditionSystem.cs @@ -0,0 +1,96 @@ +using Content.Server._Scp.Objectives.Components; +using Content.Server.Mind; +using Content.Server.Objectives.Components; +using Content.Server.Objectives.Systems; +using Content.Shared._Scp.Chaos; +using Content.Shared.Mobs.Systems; +using Content.Shared.Objectives.Components; +using Content.Shared.Roles.Jobs; +using Robust.Shared.Random; + +namespace Content.Server._Scp.Objectives.Systems; + +public sealed class ScpActivateSleepSpyConditionSystem : EntitySystem +{ + [Dependency] private readonly CodeConditionSystem _condition = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly SharedJobSystem _job = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAssigned); + SubscribeLocalEvent(OnAfterAssign); + SubscribeLocalEvent(OnGetProgress, after: [typeof(CodeConditionSystem)]); + } + + private void OnAssigned(Entity ent, ref ObjectiveAssignedEvent args) + { + var candidates = new HashSet(); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.IsAssigned) + continue; + + if (!_mobState.IsAlive(uid)) + continue; + + candidates.Add(uid); + } + + + if (candidates.Count >= 1) + { + ent.Comp.Target = _random.Pick(candidates); + TryComp(ent.Comp.Target.Value, out var sleepSpyComp); + if(sleepSpyComp != null) + sleepSpyComp.IsAssigned = true; + } + else + args.Cancelled = true; + + } + + private void OnAfterAssign(Entity ent, ref ObjectiveAfterAssignEvent args) + { + + if (ent.Comp.Target is not { } target || Deleted(target)) + return; + + if (!_mind.TryGetMind(target, out var mindId, out _)) + return; + + if (!_job.MindTryGetJob(mindId, out var jobPrototype)) + return; + + if (!_job.TryGetDepartment(jobPrototype.ID, out var departmentPrototype)) + return; + + _metaData.SetEntityName(ent, Loc.GetString("objective-condition-chaos-spy-activate-sleep-spy-title"), args.Meta); + _metaData.SetEntityDescription(ent, Loc.GetString("objective-condition-chaos-spy-activate-sleep-spy-description", ("department", Loc.GetString(departmentPrototype.Name))), args.Meta); + } + + private void OnGetProgress(Entity ent, ref ObjectiveGetProgressEvent args) + { + if (TryComp(ent, out var code) && code.Completed) + { + args.Progress = 1f; + return; + } + + if (HasComp(ent.Comp.Target)) + { + _condition.SetCompleted((ent.Owner, code), true); + args.Progress = 1f; + return; + } + + args.Progress = 0f; + } +} diff --git a/Content.Shared/_Scp/Chaos/ChaosSleepSpyMobComponent.cs b/Content.Shared/_Scp/Chaos/ChaosSleepSpyMobComponent.cs index e8a4a6c6143..7ae8d767736 100644 --- a/Content.Shared/_Scp/Chaos/ChaosSleepSpyMobComponent.cs +++ b/Content.Shared/_Scp/Chaos/ChaosSleepSpyMobComponent.cs @@ -18,6 +18,10 @@ public sealed partial class ChaosSleepSpyMobComponent : Component [DataField] public EntProtoId DefaultChaosSpyRule = "ScpChaosLowSpy"; + /// Whether this sleep spy has already been assigned as an activation target. + [ViewVariables] + public bool IsAssigned; + [ViewVariables] public string[]? CodeWords; } diff --git a/Content.Shared/_Scp/Chaos/ChaosSpyMobComponent.cs b/Content.Shared/_Scp/Chaos/ChaosSpyMobComponent.cs new file mode 100644 index 00000000000..bfe9183560b --- /dev/null +++ b/Content.Shared/_Scp/Chaos/ChaosSpyMobComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._Scp.Chaos; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ChaosSpyMobComponent : Component; diff --git a/Resources/Locale/en-US/_prototypes/_scp/objectives/chaos.ftl b/Resources/Locale/en-US/_prototypes/_scp/objectives/chaos.ftl index 225e79b6c8c..b8c7c244798 100644 --- a/Resources/Locale/en-US/_prototypes/_scp/objectives/chaos.ftl +++ b/Resources/Locale/en-US/_prototypes/_scp/objectives/chaos.ftl @@ -18,6 +18,8 @@ ent-ChaosSpyStealIdCardsObjective = { ent-BaseChaosSpyStealObjective } .desc = { ent-BaseChaosSpyStealObjective.desc } ent-ChaosSpyHelpRandomSpyObjective = { ent-BaseHelpProgressObjective } .desc = Help another Chaos spy complete their objectives. +ent-ChaosSpyActivateSleepSpyObjective = Activate the sleeper agent. + .desc = Find and activate the sleeper agent. Do this as discreetly as possible. ent-ChaosSpyStealItemScpsObjective = { ent-BaseChaosSpyStealObjective } .desc = { ent-BaseChaosSpyStealObjective.desc } ent-ChaosSpyHelpChaosRaidObjective = Help the Chaos raid. diff --git a/Resources/Locale/en-US/_strings/_scp/administration/antag.ftl b/Resources/Locale/en-US/_strings/_scp/administration/antag.ftl index f244cb68b0a..c232943b316 100644 --- a/Resources/Locale/en-US/_strings/_scp/administration/antag.ftl +++ b/Resources/Locale/en-US/_strings/_scp/administration/antag.ftl @@ -1,6 +1,9 @@ admin-verb-make-chaos-raider = Make the target into a Chaos Raider admin-verb-make-chaos-spy = Make the target into a Chaos Spy + admin-verb-make-chaos-sleep-spy = Make the target into a Chaos Sleep Spy admin-verb-text-make-chaos-raider = Make Chaos Raider + admin-verb-text-make-chaos-spy = Make Chaos Spy -admin-verb-text-make-chaos-sleep-spy = Make Chaos Sleep Spy \ No newline at end of file +admin-verb-text-make-chaos-sleep-spy = Make Chaos Sleep Spy +admin-verb-result-make-chaos-sleep-spy = Player "{$targetName}" is now a sleeper agent of the Chaos Insurgency. An objective to activate them will be given to a random active spy. diff --git a/Resources/Locale/en-US/_strings/_scp/objectives/conditions/activate-sleep-spy.ftl b/Resources/Locale/en-US/_strings/_scp/objectives/conditions/activate-sleep-spy.ftl new file mode 100644 index 00000000000..1c60e3e8391 --- /dev/null +++ b/Resources/Locale/en-US/_strings/_scp/objectives/conditions/activate-sleep-spy.ftl @@ -0,0 +1,2 @@ +objective-condition-chaos-spy-activate-sleep-spy-title = Activate the sleeper agent. +objective-condition-chaos-spy-activate-sleep-spy-description = Find and activate the sleeper agent in the "{$department}" department. Do this as discreetly as possible. diff --git a/Resources/Locale/ru-RU/_prototypes/_scp/objectives/chaos.ftl b/Resources/Locale/ru-RU/_prototypes/_scp/objectives/chaos.ftl index 5940558133b..d054750ea5d 100644 --- a/Resources/Locale/ru-RU/_prototypes/_scp/objectives/chaos.ftl +++ b/Resources/Locale/ru-RU/_prototypes/_scp/objectives/chaos.ftl @@ -18,6 +18,8 @@ ent-ChaosSpyStealIdCardsObjective = { ent-BaseChaosSpyStealObjective } .desc = { ent-BaseChaosSpyStealObjective.desc } ent-ChaosSpyHelpRandomSpyObjective = { ent-BaseHelpProgressObjective } .desc = Помогите другому шпиону Повстанцев Хаоса выполнить его цели. +ent-ChaosSpyActivateSleepSpyObjective = Активируйте спящего агента. + .desc = Найдите и активируйте спящего агента. Сделайте это как можно незаметнее. ent-ChaosSpyStealItemScpsObjective = { ent-BaseChaosSpyStealObjective } .desc = { ent-BaseChaosSpyStealObjective.desc } ent-ChaosSpyHelpChaosRaidObjective = Помочь рейдерам Повстанцев Хаоса. diff --git a/Resources/Locale/ru-RU/_strings/_scp/administration/antag.ftl b/Resources/Locale/ru-RU/_strings/_scp/administration/antag.ftl index c8f433b80f1..4a3a8493cc6 100644 --- a/Resources/Locale/ru-RU/_strings/_scp/administration/antag.ftl +++ b/Resources/Locale/ru-RU/_strings/_scp/administration/antag.ftl @@ -6,3 +6,4 @@ admin-verb-text-make-chaos-spy = Сделать шпионом Повстанц admin-verb-make-chaos-sleep-spy = Сделать цель спящим агентом Повстанцев Хаоса admin-verb-text-make-chaos-sleep-spy = Сделать спящим агентом Повстанцев Хаоса +admin-verb-result-make-chaos-sleep-spy = Игрок "{$targetName}" теперь спящий агент Повстанцев Хаоса. Цель на его активацию будет отправлена случайному активному шпиону. diff --git a/Resources/Locale/ru-RU/_strings/_scp/objectives/conditions/activate-sleep-spy.ftl b/Resources/Locale/ru-RU/_strings/_scp/objectives/conditions/activate-sleep-spy.ftl new file mode 100644 index 00000000000..7d1f200c305 --- /dev/null +++ b/Resources/Locale/ru-RU/_strings/_scp/objectives/conditions/activate-sleep-spy.ftl @@ -0,0 +1,2 @@ +objective-condition-chaos-spy-activate-sleep-spy-title = Активируйте cпящего агента. +objective-condition-chaos-spy-activate-sleep-spy-description = Найдите и активируйте спящего агента из отдела "{$department}". Сделайте это как можно незаметнее. diff --git a/Resources/Prototypes/_Scp/GameRules/roundstart.yml b/Resources/Prototypes/_Scp/GameRules/roundstart.yml index b2c18c084ac..07f44e92434 100644 --- a/Resources/Prototypes/_Scp/GameRules/roundstart.yml +++ b/Resources/Prototypes/_Scp/GameRules/roundstart.yml @@ -236,6 +236,8 @@ playerRatio: 10 pickCommandStaff: true ignoreCanBeAntag: true + components: + - type: ChaosSpyMob blacklist: components: - AntagImmune @@ -267,6 +269,8 @@ max: 5 playerRatio: 10 pickCommandStaff: false + components: + - type: ChaosSpyMob blacklist: components: - AntagImmune diff --git a/Resources/Prototypes/_Scp/Objectives/chaosSpy.yml b/Resources/Prototypes/_Scp/Objectives/chaosSpy.yml index 7b3aeff4ddc..2b988b0046f 100644 --- a/Resources/Prototypes/_Scp/Objectives/chaosSpy.yml +++ b/Resources/Prototypes/_Scp/Objectives/chaosSpy.yml @@ -146,6 +146,19 @@ components: - HelpProgressCondition +- type: entity + parent: [BaseChaosSpyObjective, BaseCodeObjective] + id: ChaosSpyActivateSleepSpyObjective + name: activate "sleeper" cells + description: Activate "sleeper" cells of the Chaos Insurgency. + components: + - type: Objective + difficulty: 1.1 + icon: + sprite: _Scp/Interface/Misc/chaos_objectives_icons.rsi + state: StealItemScp + - type: ScpActivateSleepSpyCondition + - type: entity parent: BaseChaosSpyStealObjective id: ChaosSpyStealItemScpsObjective diff --git a/Resources/Prototypes/_Scp/Objectives/objectiveGroups.yml b/Resources/Prototypes/_Scp/Objectives/objectiveGroups.yml index aaed05ed313..8c2f384b217 100644 --- a/Resources/Prototypes/_Scp/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/_Scp/Objectives/objectiveGroups.yml @@ -30,9 +30,9 @@ weights: ChaosSpyObjectiveGroupSteal: 1 #ChaosSpyObjectiveGroupDestroy: 0.6 # временно вырезано из пула тасков - ChaosSpyObjectiveGroupClassD: 0.8 + ChaosSpyObjectiveGroupClassD: 0.5 ChaosSpyObjectiveGroupKill: 0.8 - ChaosSpyObjectiveGroupSabotage: 0.8 + ChaosSpyObjectiveGroupSabotage: 0.5 ChaosSpyObjectiveGroupSocial: 0.5 - type: weightedRandom