mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Changeling Escape as Department Objective (#44220)
* insanity * changeling real pliz * the wizard * no conflict plz :3 * Update Content.Server/Objectives/Systems/ChangelingEscapeDepartmentConditionSystem.cs Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> * linq is hell * Update Content.Server/Objectives/Systems/ChangelingEscapeDepartmentConditionSystem.cs Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> * comment & API * wokism is real and is coming for you in this pride month * i am stoopid and dont know how to code :3 * cleanup, self review, usual stuff * oops * comments and difficulty * fuck everything * add to rolls * FFS * self ontag * filters? you should kill your mom, here's why women never fuck you here's how you could build a bomb. * ncat * Update Content.Shared/Roles/Jobs/SharedJobSystem.cs eqweqwewqe Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> * address slam,s reviews idfk this will be squashed anyway * from captain sprite (apparently removed on master) to atmosian sprite * Minor cleanup changes --------- Co-authored-by: nomdéraisonnablementlong <you@example.com> Co-authored-by: ScarKy0 <106310278+ScarKy0@users.noreply.github.com> Co-authored-by: Pok <113675512+Pok27@users.noreply.github.com> Co-authored-by: ScarKy0 <scarky0@onet.eu> Co-authored-by: SlamBamActionman <83650252+SlamBamActionman@users.noreply.github.com> Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
This commit is contained in:
parent
ba23ea370d
commit
5a6bf259d6
9 changed files with 279 additions and 6 deletions
|
|
@ -0,0 +1,23 @@
|
|||
using Content.Server.Objectives.Systems;
|
||||
using Content.Shared.Roles;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Requires that the changeling:
|
||||
/// 1. Is on the emergency shuttle when docking to CentComm
|
||||
/// 2. Is currently transformed into the identity of someone in the target department
|
||||
/// 3. Is wearing an ID card with the matching dept
|
||||
/// Depends on <see cref="TargetObjectiveComponent"/> to know which target department to check for.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
[Access(typeof(ChangelingEscapeDepartmentConditionSystem))]
|
||||
public sealed partial class ChangelingEscapeDepartmentConditionComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The department that is targeted by the objective.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<DepartmentPrototype>? Department;
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ public sealed partial class TargetObjectiveComponent : Component
|
|||
{
|
||||
/// <summary>
|
||||
/// Locale id for the objective title.
|
||||
/// It is passed "targetName" and "job" arguments.
|
||||
/// It is passed "targetName", "job" and "department" arguments.
|
||||
/// Standing for the name of the target, their job, and their job's department.
|
||||
/// </summary>
|
||||
[DataField(required: true), ViewVariables(VVAccess.ReadWrite)]
|
||||
public string Title = string.Empty;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Content.Server.Objectives.Components;
|
||||
using Content.Server.Shuttles.Systems;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Changeling.Systems;
|
||||
using Content.Shared.Cuffs;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Makes it so that <see cref="ChangelingEscapeDepartmentConditionComponent"/> works.
|
||||
/// </summary>
|
||||
public sealed partial class ChangelingEscapeDepartmentConditionSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private EmergencyShuttleSystem _emergencyShuttle = default!;
|
||||
[Dependency] private SharedChangelingIdentitySystem _changelingIdentity = default!;
|
||||
[Dependency] private SharedCuffableSystem _cuffable = default!;
|
||||
[Dependency] private SharedIdCardSystem _idCard = default!;
|
||||
[Dependency] private SharedMindSystem _mind = default!;
|
||||
[Dependency] private TargetObjectiveSystem _target = default!;
|
||||
[Dependency] private SharedJobSystem _job = default!;
|
||||
[Dependency] private IRobustRandom _random = default!;
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnAfterAssign(Entity<ChangelingEscapeDepartmentConditionComponent> ent, ref ObjectiveAfterAssignEvent args)
|
||||
{
|
||||
if (!_target.GetTarget(ent, out var target))
|
||||
return;
|
||||
|
||||
// Does our target have a valid job?
|
||||
if (!_job.MindTryGetJobId(target.Value, out var jobPrototype) || !jobPrototype.HasValue)
|
||||
return;
|
||||
|
||||
// We get the department of the job for our target, prioritizing the secondary departments (such as Command)
|
||||
// We do this to allow the objective to roll Command.
|
||||
// Otherwise RD/HoS/QM would return Science/Security/Cargo instead of Command.
|
||||
if (!_job.TryGetSecondaryDepartmentsOrFallback(jobPrototype.Value, out var departmentPrototypes))
|
||||
return;
|
||||
|
||||
Debug.Assert(departmentPrototypes.Count > 0, $"Attempted to assign a job without departments to objective {MetaData(ent).EntityPrototype}!");
|
||||
|
||||
// Get a random department.
|
||||
// This is all done on the server, so no need to worry about mispredictions.
|
||||
ent.Comp.Department = _random.Pick(departmentPrototypes);
|
||||
}
|
||||
|
||||
[SubscribeLocalEvent]
|
||||
private void OnGetProgress(Entity<ChangelingEscapeDepartmentConditionComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
args.Progress = GetProgress(ent, (args.MindId, args.Mind));
|
||||
}
|
||||
|
||||
private float GetProgress(Entity<ChangelingEscapeDepartmentConditionComponent> ent, Entity<MindComponent> mind)
|
||||
{
|
||||
var ownedEntity = mind.Comp.OwnedEntity;
|
||||
if (ownedEntity == null || _mind.IsCharacterDeadIc(mind))
|
||||
return 0f;
|
||||
|
||||
// Check 1: Must have transformed into a target with a job that is in the department
|
||||
if (!_changelingIdentity.TryGetCurrentIdentityData(ownedEntity.Value, out var identityData))
|
||||
return 0f; // this should not happen
|
||||
|
||||
// Our identity must have a job.
|
||||
var job = identityData.OriginalJob;
|
||||
if (!job.HasValue)
|
||||
return 0f;
|
||||
|
||||
if (ent.Comp.Department == null || !_job.JobIsInDepartment(job.Value, ent.Comp.Department.Value))
|
||||
return 0f; // The job of our identity is not in the target department or the department doesn't exist.
|
||||
|
||||
// Check 2: Must escape alive.
|
||||
if (!_emergencyShuttle.IsTargetEscaping(ownedEntity.Value))
|
||||
return 0.5f;
|
||||
|
||||
if (_cuffable.IsCuffed(ownedEntity.Value))
|
||||
return 0.5f;
|
||||
|
||||
// Check 3: Must wear an ID card with the target's name on it.
|
||||
if (!_idCard.TryFindIdCard(ownedEntity.Value, out var idCard))
|
||||
return 0.75f; // is not wearing an id
|
||||
|
||||
if (idCard.Comp.JobDepartments.All(idDept => idDept.Id != ent.Comp.Department))
|
||||
return 0.75f; // found ID is not of the correct department.
|
||||
|
||||
return 1f;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,16 @@ public sealed partial class TargetObjectiveSystem : EntitySystem
|
|||
}
|
||||
|
||||
var jobName = _job.MindTryGetJobName(target);
|
||||
return Loc.GetString(title, ("targetName", targetName), ("job", jobName));
|
||||
|
||||
var deptName = Loc.GetString("department-Unknown");
|
||||
if (_job.MindTryGetJobId(target, out var jobId))
|
||||
{
|
||||
if (jobId.HasValue && _job.TryGetDepartment(jobId.Value, out var deptProto))
|
||||
{
|
||||
deptName = Loc.GetString(deptProto.Name);
|
||||
}
|
||||
}
|
||||
return Loc.GetString(title, ("targetName", targetName), ("job", jobName), ("department", deptName));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Roles.Jobs;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.EntityConditions.Conditions.Mind;
|
||||
|
||||
public sealed partial class DifferentDepartmentConditionSystem : EntityConditionSystem<MindComponent, DifferentDepartmentCondition>
|
||||
{
|
||||
[Dependency] private SharedRoleSystem _roleSystem = default!;
|
||||
[Dependency] private SharedJobSystem _jobSystem = default!;
|
||||
|
||||
protected override void Condition(Entity<MindComponent> entity, ref EntityConditionEvent<DifferentDepartmentCondition> args)
|
||||
{
|
||||
args.Result = !IsInvalid(entity, args.SourceEnt);
|
||||
}
|
||||
|
||||
private bool IsInvalid(Entity<MindComponent> mind, EntityUid? exclude)
|
||||
{
|
||||
// no entity to exclude depts, so all depts are valid
|
||||
if (!exclude.HasValue)
|
||||
return false;
|
||||
|
||||
if (!_jobSystem.MindTryGetJobId(exclude.Value, out var objJob))
|
||||
return false; // in no department, so all departments are valid
|
||||
|
||||
if (!_jobSystem.MindTryGetJobId(mind.Owner, out var job))
|
||||
return false; // target in no department, so all depts are valid
|
||||
|
||||
if (!objJob.HasValue || !job.HasValue)
|
||||
throw new Exception("Unreachable statement after getting job proto from mind.");
|
||||
|
||||
// get all departments
|
||||
if (!_jobSystem.TryGetAllDepartments(objJob.Value, out var deptsA) || !_jobSystem.TryGetAllDepartments(job.Value, out var deptsB))
|
||||
throw new Exception("Job didn't have any department assigned.");
|
||||
|
||||
// perform the department check
|
||||
if (deptsA.Select(dept => dept.ID).Intersect(deptsB.Select(dept => dept.ID)).Any())
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A condition that requires minds to have a job with a different department from the excluded entity's.
|
||||
/// This uses mind roles, not ID cards.
|
||||
/// </summary>
|
||||
public sealed partial class DifferentDepartmentCondition : EntityConditionBase<DifferentDepartmentCondition>
|
||||
{
|
||||
public override string EntityConditionGuidebookText(IPrototypeManager prototype)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,6 +117,68 @@ public abstract partial class SharedJobSystem : EntitySystem
|
|||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Like <see cref="TryGetPrimaryDepartment"/> but tries to get any non-primary department(s) first.
|
||||
/// For all heads (including the captain), it will return Command, but for John Scientist it will return Science.
|
||||
/// </summary>
|
||||
/// <returns> True if a department was found, false if not.</returns>
|
||||
public bool TryGetSecondaryDepartmentsOrFallback(ProtoId<JobPrototype> jobProto, [NotNullWhen(true)] out List<DepartmentPrototype>? departmentPrototypes)
|
||||
{
|
||||
departmentPrototypes = new List<DepartmentPrototype>();
|
||||
|
||||
// not sorting it since there should only be 1 primary department for a job.
|
||||
// this is enforced by the job tests.
|
||||
var departmentProtos = ProtoMan.EnumeratePrototypes<DepartmentPrototype>();
|
||||
foreach (var department in departmentProtos)
|
||||
{
|
||||
if (!department.Primary && department.Roles.Contains(jobProto))
|
||||
{
|
||||
departmentPrototypes.Add(department);
|
||||
}
|
||||
}
|
||||
|
||||
if (departmentPrototypes.Count > 0)
|
||||
return true;
|
||||
|
||||
// if we don't have any secondary dept, try to get a primary
|
||||
if (TryGetPrimaryDepartment(jobProto, out var primaryDept))
|
||||
{
|
||||
departmentPrototypes.Add(primaryDept);
|
||||
return true;
|
||||
}
|
||||
|
||||
// no department was found
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all departments for a mind's job (if any) and checks if the chosen department is among them.
|
||||
/// </summary>
|
||||
/// <param name="mind">The mind to check for.</param>
|
||||
/// <param name="deptProto">The department proto ID to check for.</param>
|
||||
/// <returns>True if the mind is a part of the department.</returns>
|
||||
public bool MindIsInDepartment(EntityUid mind, ProtoId<DepartmentPrototype> deptProto)
|
||||
{
|
||||
if (!MindTryGetJobId(mind, out var job))
|
||||
return false;
|
||||
|
||||
if (!job.HasValue)
|
||||
return false;
|
||||
|
||||
return JobIsInDepartment(job.Value, deptProto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the job is contained within a department, and returns true if it does.
|
||||
/// </summary>
|
||||
public bool JobIsInDepartment(ProtoId<JobPrototype> jobProto, ProtoId<DepartmentPrototype> deptProto)
|
||||
{
|
||||
if (!TryGetAllDepartments(jobProto, out var depts))
|
||||
return false;
|
||||
|
||||
return depts.Any(dept => dept.ID == deptProto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get all the departments for a given job. Will return an empty list if none are found.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -2,3 +2,5 @@ objective-unique-identity-name = Devour {$count} unique humanoids.
|
|||
objective-unique-identity-description = We must devour {$count} different humanoids for sustenance.
|
||||
|
||||
objective-escape-identity-title = Escape as {$targetName}, {CAPITALIZE($job)}.
|
||||
|
||||
objective-escape-department-title = Escape as a member of the {CAPITALIZE($department)} department.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
description: We must stay alive at all cost.
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 1
|
||||
difficulty: 0.5
|
||||
icon:
|
||||
sprite: Mobs/Species/Human/organs.rsi
|
||||
state: heart-on
|
||||
|
|
@ -53,11 +53,37 @@
|
|||
- type: ChangelingEscapeIdentityCondition
|
||||
- type: PickRandomPerson
|
||||
conditions:
|
||||
- !type:ObjectiveTargetCondition
|
||||
- !type:RoleCondition
|
||||
inverted: true # Do not roll changelings here. Their initial identity will be basically impossible to get after they transform.
|
||||
whitelist:
|
||||
components:
|
||||
- ChangelingRole
|
||||
|
||||
- type: entity
|
||||
parent: [BaseChangelingObjective, BaseLivingObjective]
|
||||
id: ChangelingEscapeDepartmentObjective
|
||||
description: We must escape with the identity & ID card of any member of this department.
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 1
|
||||
icon:
|
||||
sprite: Objects/Fun/Plushies/atmosian.rsi
|
||||
state: icon
|
||||
- type: TargetObjective
|
||||
title: objective-escape-department-title
|
||||
- type: ChangelingEscapeDepartmentCondition
|
||||
- type: PickRandomPerson
|
||||
conditions:
|
||||
- !type:RoleCondition # Do not roll changelings here. It will cause problems with weird job mindroles as they transform n stuff.
|
||||
inverted: true
|
||||
whitelist:
|
||||
components:
|
||||
- ChangelingRole
|
||||
- !type:RoleCondition
|
||||
whitelist:
|
||||
components:
|
||||
- JobRole # Our target has to be employed, obviously.
|
||||
- !type:DifferentDepartmentCondition # I love spawning as a scientist and having to escape as a scientist
|
||||
|
||||
- type: entity
|
||||
parent: [BaseChangelingObjective, BaseKillObjective]
|
||||
|
|
|
|||
|
|
@ -157,8 +157,8 @@
|
|||
id: ChangelingObjectiveGroupEscape
|
||||
weights:
|
||||
ChangelingSurviveObjective: 0.5
|
||||
ChangelingEscapeIdentityObjective: 1
|
||||
# eventually: escape as a job/dept
|
||||
ChangelingEscapeDepartmentObjective: 1.0
|
||||
ChangelingEscapeIdentityObjective: 1.5
|
||||
|
||||
- type: weightedRandom
|
||||
id: ChangelingObjectiveGroupKillDummy
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue