Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Content.Shared._ES.Masks;
using Robust.Shared.Prototypes;

namespace Content.Server._ES.Masks.Jester.Components;

/// <summary>
/// Used for a mask that becomes another mask when they kill someone.
/// </summary>
[RegisterComponent]
public sealed partial class ESChangeMaskOnKillObjectiveComponent : Component
{
[DataField]
public LocId Message = "es-fool-conversion-notification";

[DataField(required: true)]
public ProtoId<ESMaskPrototype> Mask;

[DataField]
public float DefaultProgress = 1f;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using Content.Server._ES.Masks.Jester.Components;
using Content.Server._ES.Masks.Objectives.Relays.Components;
using Content.Server.Chat.Managers;
using Content.Shared._ES.KillTracking.Components;
using Content.Shared._ES.Objectives;
using Content.Shared._ES.Objectives.Components;
using Content.Shared.Chat;
using Robust.Server.Player;

namespace Content.Server._ES.Masks.Jester;

public sealed class ESChangeMaskOnKillObjectiveSystem : ESBaseObjectiveSystem<ESChangeMaskOnKillObjectiveComponent>
{
[Dependency] private readonly IChatManager _chat = default!;
[Dependency] private readonly IPlayerManager _player = default!;

public override Type[] RelayComponents => [typeof(ESKilledRelayComponent)];

/// <inheritdoc/>
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<ESChangeMaskOnKillObjectiveComponent, ESKilledPlayerEvent>(OnKilledPlayer);
}

private void OnKilledPlayer(Entity<ESChangeMaskOnKillObjectiveComponent> ent, ref ESKilledPlayerEvent args)
{
if (args.Suicide)
return;

// Only matters if you kill a real player with a mind
if (!MindSys.TryGetMind(args.Killed, out _))
return;

if (!MindSys.TryGetMind(args.Killer, out var mind))
return;

if (_player.TryGetSessionByEntity(args.Killer, out var session))
{
var msg = Loc.GetString(ent.Comp.Message);
var wrappedMsg = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
_chat.ChatMessageToOne(ChatChannel.Server, msg, wrappedMsg, default, false, session.Channel, Color.Red);
}

MaskSys.ChangeMask(mind.Value, ent.Comp.Mask);
}

protected override void GetObjectiveProgress(Entity<ESChangeMaskOnKillObjectiveComponent> ent, ref ESGetObjectiveProgressEvent args)
{
args.Progress = ent.Comp.DefaultProgress;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Content.Server._ES.Masks.Objectives.Relays.Components;
using Content.Server.KillTracking;
using Content.Server.Mind;
using Content.Shared._ES.KillTracking.Components;
using Content.Shared._ES.Mind;
Expand All @@ -14,6 +13,7 @@ public sealed class ESKilledRelaySystem : ESBaseMindRelay
public override void Initialize()
{
SubscribeLocalEvent<ESKilledRelayComponent, ESPlayerKilledEvent>(OnKillReported);
SubscribeLocalEvent<ESKilledRelayComponent, ESKilledPlayerEvent>(OnKilledPlayerReported);
}

private void OnKillReported(Entity<ESKilledRelayComponent> ent, ref ESPlayerKilledEvent args)
Expand All @@ -23,4 +23,12 @@ private void OnKillReported(Entity<ESKilledRelayComponent> ent, ref ESPlayerKill

RaiseMindEvent((mindId, mindComp), ref args);
}

private void OnKilledPlayerReported(Entity<ESKilledRelayComponent> ent, ref ESKilledPlayerEvent args)
{
if (!TryGetMind(ent, out var mind))
return;

RaiseMindEvent(mind.Value, ref args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,18 @@ public readonly struct ESPlayerKilledEvent(EntityUid killed, EntityUid? killer)
[MemberNotNullWhen(false, nameof(Killer))]
public bool Environment => !Killer.HasValue;
}

/// <summary>
/// Event raised on an entity when they kill and entity with <see cref="ESKillTrackerComponent"/>.
/// </summary>
[ByRefEvent]
public readonly struct ESKilledPlayerEvent(EntityUid killed, EntityUid killer)
{
public readonly EntityUid Killed = killed;

public readonly EntityUid Killer = killer;

public bool ValidKill => !Suicide;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels subjective enough it shouldn't be here OR should be a method on a system somewhere so it can become more subjective later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that there should probably be a shared interface between the kill report events and then the system can have a method that evaluates them. Rn it's kinda just lazily duped between the two events which is Whatever but obviously not ideal.


public bool Suicide => Killed == Killer;
}
43 changes: 37 additions & 6 deletions Content.Shared/_ES/KillTracking/ESKillTrackingSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,16 @@ private void RaiseKillEvent(Entity<ESKillTrackerComponent> ent)
return;
ent.Comp.Killed = true;

var killer = ent.Comp.Sources.Count switch
{
> 1 => ent.Comp.Sources.Where(s => !s.IsEnvironment).MaxBy(s => s.AccumulatedDamage)?.Entity,
1 => ent.Comp.Sources.First().Entity,
_ => null,
};
var killer = GetKiller(ent.AsNullable());

var ev = new ESPlayerKilledEvent(ent, killer);
RaiseLocalEvent(ent, ref ev, true);

if (killer.HasValue)
{
var killerEv = new ESKilledPlayerEvent(ent, killer.Value);
RaiseLocalEvent(killer.Value, ref killerEv);
}
}

private void AddDamage(Entity<ESKillTrackerComponent> ent, EntityUid? source, FixedPoint2 damage)
Expand Down Expand Up @@ -141,4 +142,34 @@ private void ReduceDamage(Entity<ESKillTrackerComponent> ent, FixedPoint2 damage
ent.Comp.Sources.Remove(source);
}
}

/// <summary>
/// Gets the "killer" of an entity, that being the entity that has
/// the most damage sources on a given entity.
/// </summary>
public EntityUid? GetKiller(Entity<ESKillTrackerComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp))
return null;

var orderedSources = GetOrderedSources(ent);
return orderedSources.FirstOrDefault()?.Entity;
}

/// <summary>
/// Returns the damage sources in a sorted order, first by non-environmental, then by damage.
/// </summary>
public List<ESDamageSource> GetOrderedSources(Entity<ESKillTrackerComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp))
return [];

if (ent.Comp.Sources.Count == 0)
return [];

return ent.Comp.Sources
.OrderBy(s => s.IsEnvironment) // Has non-environment first, then environment.
.ThenByDescending(s => s.AccumulatedDamage) // Within those groups, go from most damage to least damage.
.ToList();
}
}
1 change: 1 addition & 0 deletions Resources/Locale/en-US/_ES/masks/jester/fool.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
es-fool-conversion-notification = You've violated the comedic code of the jester's and have been sentenced to become a fool for your crimes.
3 changes: 3 additions & 0 deletions Resources/Locale/en-US/_ES/masks/masks.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ es-mask-vip-name = VIP
es-mask-vip-desc = As a VIP, use your fancy VIP card to help confirm your innocence in times of peril.

# Jester masks
es-mask-the-fool-name = Fool
es-mask-the-fool-desc = As a Fool, wallow in pity at the failure of your purpose and curse your existence to the very end.

es-mask-martyr-name = Martyr
es-mask-martyr-desc = As a Martyr, you are a beautiful soul. Be killed by another crewmember in order to ascend to heaven and drag them to hell.

Expand Down
17 changes: 16 additions & 1 deletion Resources/Prototypes/_ES/Masks/jester.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
- type: esMask
id: ESFool
name: es-mask-the-fool-name
troupe: ESJester
description: es-mask-the-fool-desc
color: olive
weight: 0
objectives:
all:
- id: ESObjectiveTheFoolMisery

- type: esMask
id: ESMartyr
name: es-mask-martyr-name
Expand All @@ -10,6 +21,7 @@
objectives:
all:
- id: ESObjectiveMartyrBeKilled
- id: ESObjectiveJesterDoNoHarm

- type: esMask
id: ESParasite
Expand All @@ -21,7 +33,9 @@
mindComponents:
- type: ESParasite
objectives:
id: ESObjectiveParasiteBeKilled
all:
- id: ESObjectiveParasiteBeKilled
- id: ESObjectiveJesterDoNoHarm

- type: esMask
id: ESPhantom
Expand All @@ -36,3 +50,4 @@
all:
- id: ESObjectivePhantomDie
- id: ESObjectivePhantomAvenge
- id: ESObjectiveJesterDoNoHarm
10 changes: 10 additions & 0 deletions Resources/Prototypes/_ES/Objectives/Jester/the_fool.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
- type: entity
parent: ESBaseMaskObjective
id: ESObjectiveTheFoolMisery
name: Wallow in misery
description: Revel in your failure after violating the comedic sanctity of the jester troupe. Pray for forgiveness you'll never receive.
components:
- type: ESObjective
icon:
sprite: Clothing/Mask/clown.rsi
state: icon
12 changes: 12 additions & 0 deletions Resources/Prototypes/_ES/Objectives/jester.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- type: entity
parent: ESBaseMaskObjective
id: ESObjectiveJesterDoNoHarm
name: Do no harm
description: While your job is to drive others to violence, by the jester's code, you must not take the life of another.
components:
- type: ESObjective
icon:
sprite: Clothing/Mask/clown.rsi
state: icon
- type: ESChangeMaskOnKillObjective
mask: ESFool
Loading