diff --git a/Content.Server/SS220/CriminalRecords/CriminalRecordSystem.cs b/Content.Server/SS220/CriminalRecords/CriminalRecordSystem.cs
index f85f00565809b6..a3c421952c4398 100644
--- a/Content.Server/SS220/CriminalRecords/CriminalRecordSystem.cs
+++ b/Content.Server/SS220/CriminalRecords/CriminalRecordSystem.cs
@@ -15,7 +15,6 @@
using Content.Shared.SS220.Ghost;
using Content.Shared.StationRecords;
using Content.Shared.StatusIcon.Components;
-using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
@@ -141,7 +140,9 @@ public void UpdateLastRecordTime(CriminalRecordCatalog catalog)
catalog.LastRecordTime = biggest == -1 ? null : biggest;
}
- public void UpdateIdCards(StationRecordKey key, GeneralStationRecord generalRecord)
+
+ /// Null if no IDCard was updated or Uid of updated IDCard
+ public EntityUid? UpdateIdCards(StationRecordKey key, GeneralStationRecord generalRecord)
{
CriminalRecord? criminalRecord = null;
if (generalRecord.CriminalRecords != null)
@@ -167,7 +168,9 @@ public void UpdateIdCards(StationRecordKey key, GeneralStationRecord generalReco
idCard.CurrentSecurityRecord = criminalRecord;
EntityManager.Dirty(uid, idCard);
+ return uid;
}
+ return null;
}
public bool RemoveCriminalRecordStatus(StationRecordKey key, int time, EntityUid? sender = null)
@@ -185,7 +188,9 @@ public bool RemoveCriminalRecordStatus(StationRecordKey key, int time, EntityUid
UpdateLastRecordTime(catalog);
_stationRecords.Synchronize(key.OriginStation);
- UpdateIdCards(key, selectedRecord);
+ var cardId = UpdateIdCards(key, selectedRecord);
+ if (cardId.HasValue && TryGetLastRecord(key, out _, out var currentCriminalRecord))
+ RaiseLocalEvent(cardId.Value, new CriminalStatusDeleted(sender, key, ref currentCriminalRecord));
if (sender != null)
{
@@ -255,7 +260,10 @@ public bool AddCriminalRecordStatus(StationRecordKey key, string message, string
catalog.LastRecordTime = currentRoundTime;
_stationRecords.Synchronize(key.OriginStation);
- UpdateIdCards(key, selectedRecord);
+ var cardId = UpdateIdCards(key, selectedRecord);
+ if (cardId.HasValue)
+ RaiseLocalEvent(cardId.Value, new CriminalStatusAdded(sender, key, ref criminalRecord));
+
_sawmill.Debug("Added new criminal record, synchonizing");
if (sender != null)
diff --git a/Content.Server/SS220/CriminalRecords/CriminalRecordsEvent.cs b/Content.Server/SS220/CriminalRecords/CriminalRecordsEvent.cs
new file mode 100644
index 00000000000000..7c7b00d2b22ca8
--- /dev/null
+++ b/Content.Server/SS220/CriminalRecords/CriminalRecordsEvent.cs
@@ -0,0 +1,27 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Shared.SS220.CriminalRecords;
+using Content.Shared.StationRecords;
+
+namespace Content.Server.SS220.CriminalRecords;
+
+public abstract class CriminalStatusEvent(EntityUid? sender, StationRecordKey key, ref CriminalRecord currentCriminalRecord)
+{
+ public EntityUid? Sender { get; } = sender;
+ public StationRecordKey Key { get; } = key;
+ public CriminalRecord CurrentCriminalRecord { get; } = currentCriminalRecord;
+}
+
+///
+/// Just Look to its name
+///
+public sealed class CriminalStatusAdded(EntityUid? sender, StationRecordKey key, ref CriminalRecord criminalRecord)
+ : CriminalStatusEvent(sender, key, ref criminalRecord)
+{ }
+
+///
+/// Just Look to its name
+///
+public sealed class CriminalStatusDeleted(EntityUid? sender, StationRecordKey key, ref CriminalRecord criminalRecord)
+ : CriminalStatusEvent(sender, key, ref criminalRecord)
+{ }
diff --git a/Content.Server/SS220/Objectives/Components/FramePersonConditionComponent.cs b/Content.Server/SS220/Objectives/Components/FramePersonConditionComponent.cs
new file mode 100644
index 00000000000000..1bfbe2e0b3535e
--- /dev/null
+++ b/Content.Server/SS220/Objectives/Components/FramePersonConditionComponent.cs
@@ -0,0 +1,14 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Server.SS220.Trackers.Components;
+
+namespace Content.Server.SS220.Objectives.Components;
+
+[RegisterComponent]
+public sealed partial class FramePersonConditionComponent : Component
+{
+ [DataField(required: true)]
+ public CriminalStatusTrackerSpecifier CriminalStatusSpecifier = new();
+
+ public bool ObjectiveIsDone = false;
+}
diff --git a/Content.Server/SS220/Objectives/Components/IntimidatePersonConditionComponent.cs b/Content.Server/SS220/Objectives/Components/IntimidatePersonConditionComponent.cs
new file mode 100644
index 00000000000000..4ee34d3c621dc5
--- /dev/null
+++ b/Content.Server/SS220/Objectives/Components/IntimidatePersonConditionComponent.cs
@@ -0,0 +1,46 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Server.SS220.Trackers.Components;
+
+namespace Content.Server.SS220.Objectives.Components;
+
+[RegisterComponent]
+public sealed partial class IntimidatePersonConditionComponent : Component
+{
+ [DataField(required: true)]
+ public DamageTrackerSpecifier DamageTrackerSpecifier = new();
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid TargetMob;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public bool ObjectiveIsDone = false;
+
+ public IntimidatePersonDescriptionType DescriptionType;
+ // Descriptions comes to help player differ done object from one which isn't.
+
+ ///
+ /// Description will be applied at start. No params in it
+ ///
+ [DataField(required: true)]
+ public string? StartDescription;
+
+ ///
+ /// Description will be applied when objective is done. No params in it
+ ///
+ [DataField(required: true)]
+ public string? SuccessDescription;
+
+ ///
+ /// Description will be applied when objective is done. No params in it
+ ///
+ [DataField(required: true)]
+ public string? SSDDescription;
+}
+
+public enum IntimidatePersonDescriptionType
+{
+ Start = 0,
+ Success,
+ SSD
+}
diff --git a/Content.Server/SS220/Objectives/Systems/FramePersonConditionSystem.cs b/Content.Server/SS220/Objectives/Systems/FramePersonConditionSystem.cs
new file mode 100644
index 00000000000000..49a2dfca3e16e4
--- /dev/null
+++ b/Content.Server/SS220/Objectives/Systems/FramePersonConditionSystem.cs
@@ -0,0 +1,140 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server.Access.Systems;
+using Content.Server.Mind;
+using Content.Server.Objectives.Components;
+using Content.Server.Objectives.Systems;
+using Content.Server.SS220.Objectives.Components;
+using Content.Server.SS220.Trackers.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.Roles;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+
+namespace Content.Server.SS220.Objectives.Systems;
+
+public sealed class FramePersonConditionSystem : EntitySystem
+{
+ [Dependency] private readonly IdCardSystem _idCard = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly SharedRoleSystem _roleSystem = default!;
+ [Dependency] private readonly TargetObjectiveSystem _targetObjective = default!;
+
+ ///
+ /// We use this to determine which jobs have legalImmunity... Wait for MRP PR for a special flag.
+ ///
+ private readonly string _legalImmunitySupervisors = "job-supervisors-centcom";
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetProgress);
+
+ SubscribeLocalEvent(OnPersonAssigned);
+ }
+
+ private void OnGetProgress(Entity entity, ref ObjectiveGetProgressEvent args)
+ {
+ if (!_targetObjective.GetTarget(entity.Owner, out var target))
+ return;
+
+ if (entity.Comp.ObjectiveIsDone)
+ {
+ args.Progress = 1f;
+ return;
+ }
+
+ if (!TryComp(target, out var mindComponent)
+ || mindComponent.OriginalOwnedEntity == null)
+ {
+ Log.Error("while getting progress: target dont have a mindComponent or originalEntity is null");
+ args.Progress = 1f;
+ return;
+ }
+
+ args.Progress = GetProgress(GetEntity(mindComponent.OriginalOwnedEntity.Value));
+ if (args.Progress >= 1f)
+ entity.Comp.ObjectiveIsDone = true;
+ }
+
+ private void OnPersonAssigned(Entity entity, ref ObjectiveAssignedEvent args)
+ {
+ args.Cancelled = !(TryPickRandomPerson(entity.Owner, args.MindId, out var target)
+ && TryComp(target, out var mindComponent)
+ && mindComponent.OriginalOwnedEntity != null
+ && TryTrackIdCardOwner(GetEntity(mindComponent.OriginalOwnedEntity.Value), args.MindId, entity.Comp));
+ }
+
+ private bool TryTrackIdCardOwner(EntityUid idCardOwner, EntityUid trackedByMind, FramePersonConditionComponent objective)
+ {
+ if (!_idCard.TryFindIdCard(idCardOwner, out var idCard))
+ return false;
+
+ var criminalStatusTracker = AddComp(idCard);
+ criminalStatusTracker.CriminalStatusSpecifier = objective.CriminalStatusSpecifier;
+ criminalStatusTracker.TrackedByMind = trackedByMind;
+ return true;
+ }
+
+ private bool TryPickRandomPerson(EntityUid objective, EntityUid objectiveOwnerMind, [NotNullWhen(true)] out EntityUid? picked, List? blacklist = null)
+ {
+ picked = null;
+ if (!TryComp(objective, out var targetObjective))
+ return false;
+
+ if (targetObjective.Target != null)
+ return false;
+
+ var whitelistedPlayers = _mind.GetAliveHumans(objectiveOwnerMind)
+ .Where(x => CorrectJob(x) && (blacklist == null || !EntityHasAnyComponent(x, blacklist)))
+ .ToList();
+
+ if (whitelistedPlayers.Count == 0)
+ return false;
+
+ picked = _random.Pick(whitelistedPlayers);
+ _targetObjective.SetTarget(objective, picked.Value, targetObjective);
+ return true;
+ }
+
+ private bool EntityHasAnyComponent(EntityUid uid, List whitelist)
+ {
+ foreach (var type in whitelist)
+ {
+ if (HasComp(uid, type))
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Checks if that job can be framed. Relays on supervisor cause... no RP in code sry.
+ ///
+ private bool CorrectJob(EntityUid mindUid)
+ {
+
+ if (!_roleSystem.MindHasRole(mindUid, out var role)
+ || !role.Value.Comp1.JobPrototype.HasValue)
+ return false;
+
+ if (_prototype.Index(role!.Value.Comp1.JobPrototype!.Value).Supervisors == _legalImmunitySupervisors)
+ return false;
+
+ return true;
+ }
+
+ private float GetProgress(EntityUid target)
+ {
+ if (!_idCard.TryFindIdCard(target, out var idCard)
+ || !TryComp(idCard, out var statusTrackerComponent))
+ return 1f; // Uh... hmmm... fuck.... <- maybe we need to change target at that point. Anyways player have no fault of that.
+
+ return statusTrackerComponent.GetProgress();
+ }
+}
diff --git a/Content.Server/SS220/Objectives/Systems/IntimidatePersonConditionSystem.cs b/Content.Server/SS220/Objectives/Systems/IntimidatePersonConditionSystem.cs
new file mode 100644
index 00000000000000..6da388cb2f8b97
--- /dev/null
+++ b/Content.Server/SS220/Objectives/Systems/IntimidatePersonConditionSystem.cs
@@ -0,0 +1,144 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using System.Linq;
+using Content.Server.Mind;
+using Content.Server.Objectives.Components;
+using Content.Server.Objectives.Systems;
+using Content.Server.SS220.Objectives.Components;
+using Content.Server.SS220.Trackers.Components;
+using Content.Shared.Mind;
+using Content.Shared.Objectives.Components;
+using Content.Shared.SSDIndicator;
+using Robust.Shared.Random;
+
+namespace Content.Server.SS220.Objectives.Systems;
+
+public sealed class IntimidatePersonConditionSystem : EntitySystem
+{
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly TargetObjectiveSystem _target = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnGetProgress);
+
+ SubscribeLocalEvent(OnPersonAssigned);
+ SubscribeLocalEvent(OnAfterAssign);
+ }
+
+ private void OnGetProgress(Entity entity, ref ObjectiveGetProgressEvent args)
+ {
+ if (!_target.GetTarget(entity.Owner, out var target))
+ return;
+
+ if (entity.Comp.ObjectiveIsDone)
+ {
+ args.Progress = 1f;
+ return;
+ }
+
+ //HandleSSDMoment
+ if (!TryComp(entity.Comp.TargetMob, out var ssdIndicator)
+ || ssdIndicator.IsSSD)
+ {
+ args.Progress = 1f;
+ SetDescription(entity, IntimidatePersonDescriptionType.SSD);
+ return;
+ }
+
+ SetDescription(entity, IntimidatePersonDescriptionType.Start);
+ args.Progress = GetProgress(entity.Comp.TargetMob);
+ if (args.Progress >= 1f)
+ {
+ entity.Comp.ObjectiveIsDone = true;
+ SetDescription(entity, IntimidatePersonDescriptionType.Success);
+ }
+ }
+
+ private void OnPersonAssigned(Entity entity, ref ObjectiveAssignedEvent args)
+ {
+ var (uid, _) = entity;
+
+ if (!TryComp(uid, out var targetObjectiveComponent))
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ if (targetObjectiveComponent.Target != null)
+ return;
+
+ var targetableMinds = _mind.GetAliveHumans(args.MindId)
+ .Where(x => TryComp(x, out var mindComponent)
+ && !HasComp(GetEntity(mindComponent.OriginalOwnedEntity)))
+ .ToList();
+
+ if (targetableMinds.Count == 0)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ var targetMindUid = _random.Pick(targetableMinds);
+ var target = GetMindsOriginalEntity(targetMindUid);
+
+ if (args.Mind.CurrentEntity == null
+ || target == null)
+ {
+ args.Cancelled = true;
+ return;
+ }
+
+ _target.SetTarget(uid, targetMindUid, targetObjectiveComponent);
+ var damageReceivedTracker = AddComp(target.Value);
+ entity.Comp.TargetMob = target.Value;
+ damageReceivedTracker.WhomDamageTrack = args.Mind.CurrentEntity.Value;
+ damageReceivedTracker.DamageTracker = entity.Comp.DamageTrackerSpecifier;
+ }
+
+ private void OnAfterAssign(Entity entity, ref ObjectiveAfterAssignEvent args)
+ {
+ if (entity.Comp.StartDescription != null)
+ _metaData.SetEntityDescription(entity.Owner, Loc.GetString(entity.Comp.StartDescription));
+ }
+
+ private float GetProgress(EntityUid target, DamageReceivedTrackerComponent? tracker = null)
+ {
+ if (!Resolve(target, ref tracker))
+ return 0f;
+
+ return tracker.GetProgress();
+ }
+
+ private EntityUid? GetMindsOriginalEntity(EntityUid mindUid)
+ {
+ return GetEntity(Comp(mindUid).OriginalOwnedEntity);
+ }
+
+ ///
+ /// A way to change description mindlessly
+ ///
+ private void SetDescription(Entity entity, IntimidatePersonDescriptionType type)
+ {
+ var (uid, component) = entity;
+ if (component.DescriptionType == type)
+ return;
+
+ var newDescription = type switch
+ {
+ IntimidatePersonDescriptionType.Start => component.StartDescription,
+ IntimidatePersonDescriptionType.Success => component.SuccessDescription,
+ IntimidatePersonDescriptionType.SSD => component.SSDDescription,
+ _ => null
+ };
+
+ if (newDescription == null)
+ return;
+
+ _metaData.SetEntityDescription(uid, Loc.GetString(newDescription));
+ }
+}
diff --git a/Content.Server/SS220/Trackers/Components/CriminalStatusTrackerComponent.cs b/Content.Server/SS220/Trackers/Components/CriminalStatusTrackerComponent.cs
new file mode 100644
index 00000000000000..e3ac74b813321b
--- /dev/null
+++ b/Content.Server/SS220/Trackers/Components/CriminalStatusTrackerComponent.cs
@@ -0,0 +1,103 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Shared.SS220.CriminalRecords;
+using Robust.Shared.Prototypes;
+using Serilog;
+
+namespace Content.Server.SS220.Trackers.Components;
+
+[RegisterComponent]
+public sealed partial class CriminalStatusTrackerComponent : Component
+{
+ ///
+ /// Mainly used to prevent starting sequence without anyone notice
+ ///
+ [ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid TrackedByMind;
+
+ [ViewVariables(VVAccess.ReadOnly)]
+ public CriminalStatusTrackerSpecifier CriminalStatusSpecifier = new();
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ private int _currentNode = InitCurrentNode;
+
+ private const int InitCurrentNode = -1;
+
+ public void ForceFirstNode() => _currentNode = InitCurrentNode;
+ public void ForceLastNode() => _currentNode = CriminalStatusSpecifier.CriminalStatusNodes.Count;
+ public float GetProgress() => (float)(_currentNode + 1) / CriminalStatusSpecifier.CriminalStatusNodes.Count;
+
+
+ ///
+ /// Hide logic for deciding if we can move further with new status. Moving to Last entry of the list is prioritized.
+ ///
+ /// True if node changed, else - false
+ public bool TryMove(ProtoId newStatus, EntityUid? mindUid)
+ {
+ if (CriminalStatusSpecifier.CriminalStatusNodes.Count == 0)
+ {
+ Log.Warning("Tried to move into empty list of criminal status nodes! This may occur to admins adding component");
+ return false;
+ }
+
+ var newNode = _currentNode + 1; // nope no ++, -- playing
+ if (TryChangeNode(newNode, newStatus, mindUid, ref _currentNode))
+ return true;
+
+ newNode = _currentNode - 1;
+ if (TryChangeNode(newNode, newStatus, mindUid, ref _currentNode))
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Checks if current step is changeable by this mind
+ ///
+ /// True if current step allowed by CriminalStatusSpecifier
+ public bool CanBeChangedByMind(EntityUid? mindUid)
+ {
+ return TrackedByMind != mindUid;
+ }
+
+ /// False if current step can be done by anyone, otherwise true.
+ public bool NeedToCheckMind(int node)
+ {
+ return !CriminalStatusSpecifier.CriminalStatusNodes[node].CanBeSetByTracker;
+ }
+
+ private bool TryChangeNode(int newNode, ProtoId newStatus, EntityUid? mindUid, ref int currentNode)
+ {
+ if (newNode <= InitCurrentNode)
+ return false;
+
+ if (newNode >= CriminalStatusSpecifier.CriminalStatusNodes.Count)
+ return false;
+
+ if (CriminalStatusSpecifier.CriminalStatusNodes[newNode].AllowedStatuses.Contains(newStatus)
+ && (!NeedToCheckMind(newNode) || CanBeChangedByMind(mindUid)))
+ {
+ currentNode = newNode;
+ return true;
+ }
+
+ return false;
+ }
+}
+
+[DataDefinition]
+public sealed partial class CriminalStatusTrackerSpecifier
+{
+ [DataField(required: true)]
+ public List CriminalStatusNodes = new();
+}
+
+[DataDefinition]
+public sealed partial class CriminalStatusTrackerSpecifierNode
+{
+ [DataField(required: true)]
+ public List> AllowedStatuses = new();
+
+ [DataField]
+ public bool CanBeSetByTracker;
+}
diff --git a/Content.Server/SS220/Trackers/Components/DamageReceivedTrackerComponent.cs b/Content.Server/SS220/Trackers/Components/DamageReceivedTrackerComponent.cs
new file mode 100644
index 00000000000000..901b9d1cb38a8e
--- /dev/null
+++ b/Content.Server/SS220/Trackers/Components/DamageReceivedTrackerComponent.cs
@@ -0,0 +1,61 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Shared.Damage.Prototypes;
+using Content.Shared.FixedPoint;
+using Content.Shared.Mobs;
+using Robust.Shared.Prototypes;
+using Serilog;
+
+namespace Content.Server.SS220.Trackers.Components;
+
+[RegisterComponent]
+public sealed partial class DamageReceivedTrackerComponent : Component
+{
+ [ViewVariables(VVAccess.ReadWrite)]
+ public EntityUid WhomDamageTrack;
+
+ // We use this to make damaging more situational like burning after igniting by performer will also counted
+ [ViewVariables(VVAccess.ReadWrite)]
+ public TimeSpan ResetTimeDamageOwnerTracked = TimeSpan.Zero;
+
+ [ViewVariables(VVAccess.ReadWrite)]
+ public FixedPoint2 CurrentAmount = 0;
+
+ [ViewVariables(VVAccess.ReadOnly)]
+ [DataField(required: true)]
+ public DamageTrackerSpecifier DamageTracker = new();
+
+ public float GetProgress()
+ {
+ if (CurrentAmount > DamageTracker.TargetAmount)
+ return 1f;
+
+ if (DamageTracker.TargetAmount == FixedPoint2.Zero)
+ {
+ Log.Warning("Damage tracker target amount is zero! This may occur due to admins adding it.");
+ return 1f;
+ }
+
+ return (float)CurrentAmount.Value / DamageTracker.TargetAmount.Value;
+ }
+
+}
+
+[DataDefinition]
+public sealed partial class DamageTrackerSpecifier
+{
+ [DataField(required: true)]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public ProtoId DamageGroup;
+
+ ///
+ /// if null will count damage in all owner's mob state.
+ ///
+ [DataField]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public List? AllowedState;
+
+ [DataField(required: true)]
+ [ViewVariables(VVAccess.ReadWrite)]
+ public FixedPoint2 TargetAmount = FixedPoint2.Zero;
+}
diff --git a/Content.Server/SS220/Trackers/Systems/CriminalStatusTrackerSystem.cs b/Content.Server/SS220/Trackers/Systems/CriminalStatusTrackerSystem.cs
new file mode 100644
index 00000000000000..96a68ac98eb33c
--- /dev/null
+++ b/Content.Server/SS220/Trackers/Systems/CriminalStatusTrackerSystem.cs
@@ -0,0 +1,33 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Server.SS220.CriminalRecords;
+using Content.Server.SS220.Trackers.Components;
+using Content.Shared.Mind.Components;
+
+
+public sealed class CriminalStatusTrackerSystem : EntitySystem
+{
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStatusChanged);
+ }
+
+ private void OnStatusChanged(Entity entity, ref CriminalStatusEvent args)
+ {
+ var (_, comp) = entity;
+
+ if (args.CurrentCriminalRecord.RecordType == null)
+ return;
+
+ EntityUid? mindUid = null;
+ // we check if sender is able to move the progress
+ if (TryComp(args.Sender, out var mindContainer))
+ mindUid = mindContainer.Mind;
+
+ comp.TryMove(args.CurrentCriminalRecord.RecordType.Value, mindUid);
+ }
+}
+
diff --git a/Content.Server/SS220/Trackers/Systems/DamageReceivedTrackerSystem.cs b/Content.Server/SS220/Trackers/Systems/DamageReceivedTrackerSystem.cs
new file mode 100644
index 00000000000000..61bbab78303045
--- /dev/null
+++ b/Content.Server/SS220/Trackers/Systems/DamageReceivedTrackerSystem.cs
@@ -0,0 +1,47 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using Content.Server.SS220.Trackers.Components;
+using Content.Shared.Damage;
+using Content.Shared.Mobs.Components;
+using Content.Shared.Mobs.Systems;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Server.SS220.Trackers.Systems;
+
+public sealed class DamageReceivedTrackerSystem : EntitySystem
+{
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ private const float ResetDamageOwnerDelaySeconds = 0.5f;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnDamageChanged, before: [typeof(MobThresholdSystem)]);
+ }
+
+ private void OnDamageChanged(Entity entity, ref DamageChangedEvent args)
+ {
+
+ if (args.DamageDelta == null || !args.DamageIncreased)
+ return;
+
+ if (args.Origin != entity.Comp.WhomDamageTrack
+ && entity.Comp.ResetTimeDamageOwnerTracked < _gameTiming.CurTime)
+ return;
+
+ if (entity.Comp.DamageTracker.AllowedState != null
+ && (!TryComp(entity.Owner, out var mobState)
+ || entity.Comp.DamageTracker.AllowedState!.Contains(mobState.CurrentState)))
+ return;
+
+ var damageGroup = _prototype.Index(entity.Comp.DamageTracker.DamageGroup);
+ args.DamageDelta.TryGetDamageInGroup(damageGroup, out var trackableDamage);
+ entity.Comp.CurrentAmount += trackableDamage;
+
+ entity.Comp.ResetTimeDamageOwnerTracked = _gameTiming.CurTime + TimeSpan.FromSeconds(ResetDamageOwnerDelaySeconds);
+ }
+}
diff --git a/Resources/Locale/ru-RU/ss220/objectives/ninja.ftl b/Resources/Locale/ru-RU/ss220/objectives/ninja.ftl
new file mode 100644
index 00000000000000..86d6b1afbb00c1
--- /dev/null
+++ b/Resources/Locale/ru-RU/ss220/objectives/ninja.ftl
@@ -0,0 +1,18 @@
+objective-condition-frame-target-title = Подставьте { $targetName }, в должности { CAPITALIZE($job) }.
+ent-NinjaFrameTargetObjective = Подставить цель
+ .desc = Этот сотрудник избегает правосудия, исправьте это. Подставьте цель так, что дойдёт до заключения в бриге станции. Следите чтобы пометки оставались и в криминальных записях.
+
+objective-condition-intimidate-target-brute-title = Заказ на устрашение { $targetName }, в должности { CAPITALIZE($job) }.
+objective-condition-intimidate-target-brute-desc = Вам заказали преподать урок члену экипажа станции. Избейте вашу цель несколько раз, пока она в ясном сознании.
+objective-condition-intimidate-target-brute-desc-ssd= Цель впала в ССД, такой исход тоже возможен.
+objective-condition-intimidate-target-brute-desc-success = Вы успешно преподали урок.
+
+objective-condition-intimidate-target-burn-title = Заказ на устрашение { $targetName }, в должности { CAPITALIZE($job) }.
+objective-condition-intimidate-target-burn-desc = Вам заказали преподать урок члену экипажа станции. Нанесите вашей цели достаточно ожогов, пока она в ясном сознании.
+objective-condition-intimidate-target-burn-desc-ssd= Цель впала в ССД, такой исход тоже возможен.
+objective-condition-intimidate-target-burn-desc-success = Вы успешно преподали урок.
+
+objective-condition-intimidate-target-toxin-title = Заказ на устрашение { $targetName }, в должности { CAPITALIZE($job) }.
+objective-condition-intimidate-target-toxin-desc = Вам заказали преподать урок члену экипажа станции. Отравите вашу цель несколько раз, пока она в ясном сознании.
+objective-condition-intimidate-target-toxin-desc-ssd= Цель впала в ССД, такой исход тоже возможен.
+objective-condition-intimidate-target-toxin-desc-success = Вы успешно преподали урок.
diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml
index 8e6f516fd02b53..d7a87e8f3a6193 100644
--- a/Resources/Prototypes/GameRules/events.yml
+++ b/Resources/Prototypes/GameRules/events.yml
@@ -204,12 +204,19 @@
- type: AntagLoadProfileRule
- type: AntagObjectives
objectives:
+ - NinjaFrameTargetObjective # SS220 Ninja-target-update | Move it upper
- StealResearchObjective
- DoorjackObjective
- - SpiderChargeObjective
+ # - SpiderChargeObjective # SS220 Ninja-target-update
- TerrorObjective
- - MassArrestObjective
+ # - MassArrestObjective # SS220 Ninja-target-update
- NinjaSurviveObjective
+ # SS220 Ninja-target-update begin
+ - type: AntagRandomObjectives
+ sets:
+ - groups: NinjaObjectiveGroups
+ maxDifficulty: 1.5 # be careful 1.5 is here because one ninjaTarget cost 1.5 and we need only one
+ # SS220 Ninja-target-update end
- type: AntagSelection
agentName: ninja-round-end-agent-name
definitions:
diff --git a/Resources/Prototypes/Roles/Antags/ninja.yml b/Resources/Prototypes/Roles/Antags/ninja.yml
index 6a9a65bb13ec87..ab26d5a7437d52 100644
--- a/Resources/Prototypes/Roles/Antags/ninja.yml
+++ b/Resources/Prototypes/Roles/Antags/ninja.yml
@@ -20,7 +20,7 @@
shoes: ClothingShoesSpaceNinja
id: AgentIDCard
ears: ClothingHeadsetGrey
- pocket1: SpiderCharge
+ # pocket1: SpiderCharge # SS220 Ninja-target-update
pocket2: PinpointerStation
belt: EnergyKatana
inhand:
diff --git a/Resources/Prototypes/SS220/Objectives/ninja.yml b/Resources/Prototypes/SS220/Objectives/ninja.yml
new file mode 100644
index 00000000000000..d7f67c29875d8d
--- /dev/null
+++ b/Resources/Prototypes/SS220/Objectives/ninja.yml
@@ -0,0 +1,82 @@
+- type: entity
+ parent: BaseNinjaObjective
+ id: NinjaFrameTargetObjective
+ description: This person fall out of NanoTrasen attention, correct it. Security must imprison them.
+ components:
+ - type: Objective
+ icon:
+ sprite: SS220/Objectives/frame.rsi
+ state: snake_white
+ - type: TargetObjective
+ title: objective-condition-frame-target-title
+ - type: FramePersonCondition
+ criminalStatusSpecifier:
+ criminalStatusNodes:
+ - !type:CriminalStatusTrackerSpecifierNode
+ allowedStatuses: [wanted, search, execute, suspected]
+ canBeSetByTracker: true
+ - !type:CriminalStatusTrackerSpecifierNode
+ allowedStatuses: [incarcerated]
+ canBeSetByTracker: false # it will be very sad if ninja can just hack its target
+ - !type:CriminalStatusTrackerSpecifierNode
+ allowedStatuses: [discharged]
+ canBeSetByTracker: true
+
+- type: entity
+ parent: BaseNinjaObjective
+ id: NinjaIntimidateBruteTargetObjective
+ components:
+ - type: Objective
+ icon:
+ sprite: SS220/Objectives/intimidate.rsi
+ state: brute
+ - type: TargetObjective
+ title: objective-condition-intimidate-target-brute-title
+ - type: IntimidatePersonCondition
+ startDescription: objective-condition-intimidate-target-brute-desc
+ successDescription: objective-condition-intimidate-target-brute-desc-success
+ sSDDescription: objective-condition-intimidate-target-brute-desc-ssd
+ damageTrackerSpecifier:
+ damageGroup: Brute
+ allowedState: [Alive]
+ targetAmount: 280
+
+- type: entity
+ parent: BaseNinjaObjective
+ id: NinjaIntimidateBurnTargetObjective
+ components:
+ - type: Objective
+ icon:
+ sprite: SS220/Objectives/intimidate.rsi
+ state: burn
+ - type: TargetObjective
+ title: objective-condition-intimidate-target-burn-title
+ - type: IntimidatePersonCondition
+ startDescription: objective-condition-intimidate-target-burn-desc
+ successDescription: objective-condition-intimidate-target-burn-desc-success
+ sSDDescription: objective-condition-intimidate-target-burn-desc-ssd
+ damageTrackerSpecifier:
+ damageGroup: Burn
+ allowedState: [Alive]
+ targetAmount: 160
+
+- type: entity
+ parent: BaseNinjaObjective
+ id: NinjaIntimidateToxinTargetObjective
+ components:
+ - type: Objective
+ icon:
+ sprite: SS220/Objectives/intimidate.rsi
+ state: toxin
+ - type: TargetObjective
+ title: objective-condition-intimidate-target-toxin-title
+ - type: IntimidatePersonCondition
+ startDescription: objective-condition-intimidate-target-toxin-desc
+ successDescription: objective-condition-intimidate-target-toxin-desc-success
+ sSDDescription: objective-condition-intimidate-target-toxin-desc-ssd
+ damageTrackerSpecifier:
+ damageGroup: Toxin
+ allowedState: [Alive]
+ targetAmount: 120
+
+
diff --git a/Resources/Prototypes/SS220/Objectives/ninjaGroup.yml b/Resources/Prototypes/SS220/Objectives/ninjaGroup.yml
new file mode 100644
index 00000000000000..7ce3ebf8baa967
--- /dev/null
+++ b/Resources/Prototypes/SS220/Objectives/ninjaGroup.yml
@@ -0,0 +1,11 @@
+- type: weightedRandom
+ id: NinjaObjectiveGroups
+ weights:
+ NinjaIntimidateTargetGroup: 1
+
+- type: weightedRandom
+ id: NinjaIntimidateTargetGroup
+ weights:
+ NinjaIntimidateBruteTargetObjective: 5 # easiest
+ NinjaIntimidateBurnTargetObjective: 3
+ NinjaIntimidateToxinTargetObjective: 1 # hardest
diff --git a/Resources/Textures/SS220/Objectives/frame.rsi/meta.json b/Resources/Textures/SS220/Objectives/frame.rsi/meta.json
new file mode 100644
index 00000000000000..989459a5ead9bd
--- /dev/null
+++ b/Resources/Textures/SS220/Objectives/frame.rsi/meta.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "license": "EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt",
+ "copyright": "By Estkemran (Github) for SS220 for SS220",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "snake_cian"
+ },
+ {
+ "name": "snake_green"
+ },
+ {
+ "name": "snake_white"
+ }
+ ]
+}
diff --git a/Resources/Textures/SS220/Objectives/frame.rsi/snake_cian.png b/Resources/Textures/SS220/Objectives/frame.rsi/snake_cian.png
new file mode 100644
index 00000000000000..7a0e8df7777734
Binary files /dev/null and b/Resources/Textures/SS220/Objectives/frame.rsi/snake_cian.png differ
diff --git a/Resources/Textures/SS220/Objectives/frame.rsi/snake_green.png b/Resources/Textures/SS220/Objectives/frame.rsi/snake_green.png
new file mode 100644
index 00000000000000..5af12744261d82
Binary files /dev/null and b/Resources/Textures/SS220/Objectives/frame.rsi/snake_green.png differ
diff --git a/Resources/Textures/SS220/Objectives/frame.rsi/snake_white.png b/Resources/Textures/SS220/Objectives/frame.rsi/snake_white.png
new file mode 100644
index 00000000000000..905ff0405250d8
Binary files /dev/null and b/Resources/Textures/SS220/Objectives/frame.rsi/snake_white.png differ
diff --git a/Resources/Textures/SS220/Objectives/intimidate.rsi/brute.png b/Resources/Textures/SS220/Objectives/intimidate.rsi/brute.png
new file mode 100644
index 00000000000000..64800643b078fe
Binary files /dev/null and b/Resources/Textures/SS220/Objectives/intimidate.rsi/brute.png differ
diff --git a/Resources/Textures/SS220/Objectives/intimidate.rsi/burn.png b/Resources/Textures/SS220/Objectives/intimidate.rsi/burn.png
new file mode 100644
index 00000000000000..e0b8744ea4632f
Binary files /dev/null and b/Resources/Textures/SS220/Objectives/intimidate.rsi/burn.png differ
diff --git a/Resources/Textures/SS220/Objectives/intimidate.rsi/meta.json b/Resources/Textures/SS220/Objectives/intimidate.rsi/meta.json
new file mode 100644
index 00000000000000..91c836062dc554
--- /dev/null
+++ b/Resources/Textures/SS220/Objectives/intimidate.rsi/meta.json
@@ -0,0 +1,20 @@
+{
+ "version": 1,
+ "license": "EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt",
+ "copyright": "By Estkemran (Github) for SS220 for SS220",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "brute"
+ },
+ {
+ "name": "burn"
+ },
+ {
+ "name": "toxin"
+ }
+ ]
+}
diff --git a/Resources/Textures/SS220/Objectives/intimidate.rsi/toxin.png b/Resources/Textures/SS220/Objectives/intimidate.rsi/toxin.png
new file mode 100644
index 00000000000000..5524c78d239d70
Binary files /dev/null and b/Resources/Textures/SS220/Objectives/intimidate.rsi/toxin.png differ