Skip to content

Commit

Permalink
Move out evil twin from Secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
Morb0 committed Nov 12, 2023
1 parent f06a3e7 commit 7cbdc99
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 1 deletion.
7 changes: 7 additions & 0 deletions Content.Server/Corvax/EvilTwin/EvilTwinComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Content.Server.Corvax.EvilTwin;

[RegisterComponent]
public sealed partial class EvilTwinComponent : Component
{
public EntityUid TargetMindId;
}
6 changes: 6 additions & 0 deletions Content.Server/Corvax/EvilTwin/EvilTwinSpawnerComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Content.Server.Corvax.EvilTwin;

[RegisterComponent]
public sealed partial class EvilTwinSpawnerComponent : Component
{
}
241 changes: 241 additions & 0 deletions Content.Server/Corvax/EvilTwin/EvilTwinSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Content.Server.DetailExaminable;
using Content.Server.GameTicking;
using Content.Server.Humanoid;
using Content.Server.Jobs;
using Content.Server.Mind;
using Content.Server.Objectives.Components;
using Content.Server.Objectives.Systems;
using Content.Server.Preferences.Managers;
using Content.Server.Roles;
using Content.Server.Roles.Jobs;
using Content.Server.Station.Systems;
using Content.Shared.Humanoid;
using Content.Shared.Mind.Components;
using Content.Shared.Objectives.Components;
using Content.Shared.Objectives.Systems;
using Content.Shared.Preferences;
using Content.Shared.Roles;
using Robust.Server.GameObjects;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;

namespace Content.Server.Corvax.EvilTwin;

public sealed class EvilTwinSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly IServerPreferencesManager _prefs = default!;
[Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!;
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
[Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly RoleSystem _roleSystem = default!;
[Dependency] private readonly JobSystem _jobSystem = default!;
[Dependency] private readonly MetaDataSystem _metaDataSystem = default!;
[Dependency] private readonly TargetObjectiveSystem _target = default!;
[Dependency] private readonly SharedObjectivesSystem _objectives = default!;

[ValidatePrototypeId<AntagPrototype>]
private const string EvilTwinRole = "EvilTwin";
[ValidatePrototypeId<EntityPrototype>]
private const string KillObjective = "KillTwinObjective";
[ValidatePrototypeId<EntityPrototype>]
private const string EscapeObjective = "EscapeShuttleTwinObjective";

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<EvilTwinSpawnerComponent, PlayerAttachedEvent>(OnPlayerAttached);
SubscribeLocalEvent<EvilTwinComponent, MindAddedMessage>(OnMindAdded);
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEnd);
}

private void OnPlayerAttached(EntityUid uid, EvilTwinSpawnerComponent component, PlayerAttachedEvent args)
{
if (TryGetEligibleHumanoid(out var targetUid))
{
var spawnerCoords = Transform(uid).Coordinates;
var spawnedTwin = TrySpawnEvilTwin(targetUid.Value, spawnerCoords);
if (spawnedTwin != null &&
_mindSystem.TryGetMind(args.Player, out var mindId, out var mind))
{
mind.CharacterName = MetaData(spawnedTwin.Value).EntityName;
_mindSystem.TransferTo(mindId, spawnedTwin);
}
}

QueueDel(uid);
}

private void OnMindAdded(EntityUid uid, EvilTwinComponent component, MindAddedMessage args)
{
if (!TryComp<EvilTwinComponent>(uid, out var evilTwin) ||
!_mindSystem.TryGetMind(uid, out var mindId, out var mind))
return;

var role = new TraitorRoleComponent
{
PrototypeId = EvilTwinRole,
};
_roleSystem.MindAddRole(mindId, role, mind);
_mindSystem.TryAddObjective(mindId, mind, EscapeObjective);
_mindSystem.TryAddObjective(mindId, mind, KillObjective);
if (TryComp<TargetObjectiveComponent>(uid, out var targetObj))
_target.SetTarget(uid, evilTwin.TargetMindId, targetObj);
}

private void OnRoundEnd(RoundEndTextAppendEvent ev)
{
var twinsCount = EntityQuery<EvilTwinComponent>().Count();
if (twinsCount == 0)
return;

var result = Loc.GetString("evil-twin-round-end-result", ("evil-twin-count", twinsCount));

var query = EntityQueryEnumerator<EvilTwinComponent>();
while (query.MoveNext(out var uid, out var twin))
{
if (!_mindSystem.TryGetMind(uid, out var mindId, out var mind))
continue;

var name = mind.CharacterName;
_mindSystem.TryGetSession(mind.OwnedEntity, out var session);
var username = session?.Name;

var objectives = mind.Objectives.ToArray();
if (objectives.Length == 0)
{
if (username != null)
{
if (name == null)
result += "\n" + Loc.GetString("evil-twin-user-was-an-evil-twin", ("user", username));
else
result += "\n" + Loc.GetString("evil-twin-user-was-an-evil-twin-named", ("user", username), ("name", name));
}
else if (name != null)
result += "\n" + Loc.GetString("evil-twin-was-an-evil-twin-named", ("name", name));

continue;
}

if (username != null)
{
if (name == null)
result += "\n" + Loc.GetString("evil-twin-user-was-an-evil-twin-with-objectives", ("user", username));
else
result += "\n" + Loc.GetString("evil-twin-user-was-an-evil-twin-with-objectives-named", ("user", username), ("name", name));
}
else if (name != null)
result += "\n" + Loc.GetString("evil-twin-was-an-evil-twin-with-objectives-named", ("name", name));

foreach (var objectiveGroup in objectives.GroupBy(o => Comp<ObjectiveComponent>(o).Issuer))
{
foreach (var objective in objectiveGroup)
{
var info = _objectives.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")
);
}
}
}
}
ev.AddLine(result);
}

/// <summary>
/// Get first random humanoid controlled by player mob with job
/// </summary>
/// <param name="uid">Found humanoid uid</param>
/// <returns>false if not found</returns>
private bool TryGetEligibleHumanoid([NotNullWhen(true)] out EntityUid? uid)
{
var targets = EntityQuery<ActorComponent, HumanoidAppearanceComponent>().ToList();
_random.Shuffle(targets);
foreach (var (actor, _) in targets)
{
if (!_mindSystem.TryGetMind(actor.PlayerSession, out var mindId, out var mind) || mind.OwnedEntity == null)
continue;

if (!_jobSystem.MindTryGetJob(mindId, out _, out _))
continue;

// There was check for nukeops or evil twin, but ist it will be fun?

uid = mind.OwnedEntity;
return true;
}

uid = null;
return false;
}

/// <summary>
/// Spawns "clone" in round start state of target human mob
/// </summary>
/// <param name="target">Target for cloning</param>
/// <param name="coords">Spawn location</param>
/// <returns>null if target in invalid state (ghost, leave, ...)</returns>
private EntityUid? TrySpawnEvilTwin(EntityUid target, EntityCoordinates coords)
{
if (!_mindSystem.TryGetMind(target, out var mindId, out _) ||
!TryComp<HumanoidAppearanceComponent>(target, out var humanoid) ||
!TryComp<ActorComponent>(target, out var actor) ||
!_prototype.TryIndex(humanoid.Species, out var species))
return null;

var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(actor.PlayerSession.UserId).SelectedCharacter;

var twinUid = Spawn(species.Prototype, coords);
_humanoid.LoadProfile(twinUid, pref);
_metaDataSystem.SetEntityName(twinUid, MetaData(target).EntityName);
if (TryComp<DetailExaminableComponent>(target, out var detail))
{
var detailCopy = EnsureComp<DetailExaminableComponent>(twinUid);
detailCopy.Content = detail.Content;
}

if (_jobSystem.MindTryGetJob(mindId, out _, out var jobProto) && jobProto.StartingGear != null)
{
if (_prototype.TryIndex<StartingGearPrototype>(jobProto.StartingGear, out var gear))
{
_stationSpawning.EquipStartingGear(twinUid, gear, pref);
_stationSpawning.EquipIdCard(twinUid, pref.Name, jobProto, _stationSystem.GetOwningStation(target));
}

foreach (var special in jobProto.Special)
{
if (special is AddComponentSpecial)
special.AfterEquip(twinUid);
}
}

EnsureComp<EvilTwinComponent>(twinUid).TargetMindId = mindId;

return twinUid;
}
}

18 changes: 18 additions & 0 deletions Resources/Locale/ru-RU/corvax/station-events/events/evil-twin.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
evil-twin-round-end-result =
{ $evil-twin-count ->
[one] Был один
*[other] Было { $evil-twin-count }
} { $evil-twin-count ->
[one] злой двойник
[few] злых двойника
*[other] злых двойников
}.
evil-twin-user-was-an-evil-twin = [color=gray]{ $user }[/color] был злым двойником.
evil-twin-user-was-an-evil-twin-named = [color=white]{ $name }[/color] ([color=gray]{ $user }[/color]) был злым двойником.
evil-twin-was-an-evil-twin-named = [color=white]{ $name }[/color] был злым двойником.
evil-twin-user-was-an-evil-twin-with-objectives = [color=gray]{ $user }[/color] был(а) злым двойником со следующими целями:
evil-twin-user-was-an-evil-twin-with-objectives-named = [color=White]{ $name }[/color] ([color=gray]{ $user }[/color]) был(а) злым двойником со следующими целями:
evil-twin-was-an-evil-twin-with-objectives-named = [color=white]{ $name }[/color] был(а) злым двойником со следующими целями:
roles-antag-evil-twin-name = Злой двойник
roles-antag-evil-twin-objective = Ваша задача - устранение и замена оригинальной персоны.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
- type: entity
id: SpawnPointEvilTwin
name: evil twin spawn point
parent: MarkerBase
components:
- type: EvilTwinSpawner
- type: GhostRole
name: Злой двойник
description: Вы - злой двойник какой-то другой персоны.
rules: |
Старайтесь действовать скрытно, никто не должен прознать о подмене!
Действуйте от лица вашего оригинала, хитрите, подставляйте, запутывайте.
- type: GhostTakeoverAvailable
- type: Sprite
sprite: Markers/jobs.rsi
layers:
- state: green
- sprite: Mobs/Ghosts/ghost_human.rsi
state: icon
14 changes: 14 additions & 0 deletions Resources/Prototypes/Corvax/GameRules/events.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
- type: entity
id: EvilTwin
parent: BaseGameRule
noSpawn: true
components:
- type: StationEvent
minimumPlayers: 2
weight: 5
duration: 1
reoccurrenceDelay: 40
- type: VentCrittersRule
specialEntries:
- id: SpawnPointEvilTwin
prob: 0.0
26 changes: 26 additions & 0 deletions Resources/Prototypes/Corvax/Objectives/eviltwin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
- type: entity
noSpawn: true
parent: [BaseTraitorObjective, BaseLivingObjective]
id: EscapeShuttleTwinObjective
name: Escape to centcom alive and unrestrained.
description: Continue your covert implementation already on Centcom.
components:
- type: Objective
difficulty: 1.3
icon:
sprite: Structures/Furniture/chairs.rsi
state: shuttle
- type: EscapeShuttleCondition

- type: entity
noSpawn: true
parent: [BaseTraitorObjective, BaseKillObjective]
id: KillTwinObjective
name: Kill original persona.
description: Kill your original persona and take his place.
components:
- type: Objective
difficulty: 1.75
unique: false
- type: TargetObjective
title: objective-condition-kill-person-title
6 changes: 6 additions & 0 deletions Resources/Prototypes/Corvax/Roles/Antags/eviltwin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- type: antag
id: EvilTwin
name: roles-antag-evil-twin-name
antagonist: true
setPreference: false
objective: roles-antag-evil-twin-objective
2 changes: 1 addition & 1 deletion Secrets

0 comments on commit 7cbdc99

Please sign in to comment.