Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revenants enter stasis on death #262

Merged
merged 11 commits into from
Sep 16, 2024
Merged
9 changes: 9 additions & 0 deletions Content.Client/Revenant/RevenantStasisComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Robust.Shared.GameStates;

namespace Content.Client.Revenant;

[RegisterComponent]
[NetworkedComponent]
public sealed partial class RevenantStasisComponent : Component
{
}
25 changes: 25 additions & 0 deletions Content.Client/Revenant/RevenantStasisSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using Content.Shared.Examine;
using Content.Shared.Interaction.Events;

namespace Content.Client.Revenant;

public sealed partial class RevenantStasisSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<RevenantStasisComponent, ChangeDirectionAttemptEvent>(OnAttemptDirection);
SubscribeLocalEvent<RevenantStasisComponent, ExaminedEvent>(OnExamine);
}

private void OnExamine(Entity<RevenantStasisComponent> entity, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("revenant-stasis-regenerating"));
}

private void OnAttemptDirection(EntityUid uid, RevenantStasisComponent comp, ChangeDirectionAttemptEvent args)
{
args.Cancel();
}
}
6 changes: 6 additions & 0 deletions Content.Server/Construction/ConstructionSystem.Initial.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ void ShutdownContainers()
}
}

// Inform consumed items that they have been consumed
foreach (var entity in container.ContainedEntities.ToArray())
{
RaiseLocalEvent(entity, new ConstructionConsumedObjectEvent(entity, newEntity));
}

// We now get rid of all them.
ShutdownContainers();

Expand Down
12 changes: 12 additions & 0 deletions Content.Server/Construction/ConstructionSystem.Interactions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -628,4 +628,16 @@ public sealed class OnConstructionTemperatureEvent : HandledEntityEventArgs
{
public HandleResult? Result;
}

public sealed class ConstructionConsumedObjectEvent : EntityEventArgs
{
public EntityUid Old;
public EntityUid New;

public ConstructionConsumedObjectEvent(EntityUid oldEnt, EntityUid newEnt)
{
Old = oldEnt;
New = newEnt;
}
}
}
34 changes: 30 additions & 4 deletions Content.Server/Kitchen/EntitySystems/ReagentGrinderSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,14 @@ public override void Update(float frameTime)
if (outputContainer is null || !_solutionContainersSystem.TryGetFitsInDispenser(outputContainer.Value, out var containerSoln, out var containerSolution))
continue;

List<EntityUid> toDelete = new();
List<(EntityUid, int)> toSet = new();

foreach (var item in inputContainer.ContainedEntities.ToList())
{
var solution = active.Program switch
{
GrinderProgram.Grind => GetGrindSolution(item),
GrinderProgram.Grind => TryGrindSolution(item, (uid, reagentGrinder), inputContainer.ContainedEntities),
GrinderProgram.Juice => CompOrNull<ExtractableComponent>(item)?.JuiceSolution,
_ => null,
};
Expand All @@ -115,19 +118,24 @@ public override void Update(float frameTime)
scaledSolution.ScaleSolution(fitsCount);
solution = scaledSolution;

_stackSystem.SetCount(item, stack.Count - fitsCount); // Setting to 0 will QueueDel
toSet.Add((item, stack.Count - fitsCount));
}
else
{
if (solution.Volume > containerSolution.AvailableVolume)
continue;

QueueDel(item);
toDelete.Add(item);
}

_solutionContainersSystem.TryAddSolution(containerSoln.Value, solution);
}

foreach (var item in toDelete)
Del(item);
foreach (var (item, amount) in toSet)
_stackSystem.SetCount(item, amount); // Setting to 0 will QueueDel

_userInterfaceSystem.ServerSendUiMessage(uid, ReagentGrinderUiKey.Key,
new ReagentGrinderWorkCompleteMessage());

Expand Down Expand Up @@ -315,12 +323,18 @@ private void ClickSound(Entity<ReagentGrinderComponent> reagentGrinder)
_audioSystem.PlayPvs(reagentGrinder.Comp.ClickSound, reagentGrinder.Owner, AudioParams.Default.WithVolume(-2f));
}

private Solution? GetGrindSolution(EntityUid uid)
private Solution? TryGrindSolution(EntityUid uid, Entity<ReagentGrinderComponent> grinder, IReadOnlyList<EntityUid> contents)
{
if (TryComp<ExtractableComponent>(uid, out var extractable)
&& extractable.GrindableSolution is not null
&& _solutionContainersSystem.TryGetSolution(uid, extractable.GrindableSolution, out _, out var solution))
{
var ev = new GrindAttemptEvent(grinder, contents);
RaiseLocalEvent(uid, ev);

if (ev.Cancelled)
return null;

return solution;
}
else
Expand All @@ -339,4 +353,16 @@ private bool CanJuice(EntityUid uid)
return CompOrNull<ExtractableComponent>(uid)?.JuiceSolution is not null;
}
}

public sealed partial class GrindAttemptEvent : CancellableEntityEventArgs
{
public Entity<ReagentGrinderComponent> Grinder;
public IReadOnlyList<EntityUid> Reagents;

public GrindAttemptEvent(Entity<ReagentGrinderComponent> grinder, IReadOnlyList<EntityUid> reagents)
{
Grinder = grinder;
Reagents = reagents;
}
}
}
23 changes: 23 additions & 0 deletions Content.Server/Revenant/Components/RevenantStatisComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using Content.Server.Revenant.EntitySystems;
using Content.Shared.Revenant.Components;
using Robust.Shared.GameStates;

namespace Content.Server.Revenant.Components;

[RegisterComponent]
[Access(typeof(RevenantStasisSystem))]
[NetworkedComponent]
public sealed partial class RevenantStasisComponent : Component
{
[ViewVariables(VVAccess.ReadOnly)]
public Entity<RevenantComponent> Revenant;

[ViewVariables(VVAccess.ReadOnly)]
public TimeSpan StasisDuration = TimeSpan.FromSeconds(60);

public RevenantStasisComponent(TimeSpan stasisDuration, Entity<RevenantComponent> revenant)
{
StasisDuration = stasisDuration;
Revenant = revenant;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private void OnComponentStartup(Entity<RevenantAnimatedComponent> ent, ref Compo
if (HasComp<ItemToggleMeleeWeaponComponent>(ent.Owner) && TryComp<ItemToggleComponent>(ent.Owner, out var toggle))
_itemToggleSystem.TryActivate((ent.Owner, toggle));

_popup.PopupEntity(Loc.GetString("revenant-animate-item-animate", ("name", Comp<MetaDataComponent>(ent.Owner).EntityName)), ent.Owner, Filter.Pvs(ent.Owner), true);
_popup.PopupEntity(Loc.GetString("revenant-animate-item-animate", ("target", ent.Owner)), ent.Owner, Filter.Pvs(ent.Owner), true);

// Add melee damage if an item doesn't already have it
if (EnsureHelper<MeleeWeaponComponent>(ent, out var melee))
Expand Down Expand Up @@ -144,7 +144,7 @@ private void OnComponentShutdown(Entity<RevenantAnimatedComponent> ent, ref Comp
RemCompDeferred(ent, comp);
}

_popup.PopupEntity(Loc.GetString("revenant-animate-item-inanimate", ("name", Comp<MetaDataComponent>(ent).EntityName)), ent, Filter.Pvs(ent), true);
_popup.PopupEntity(Loc.GetString("revenant-animate-item-inanimate", ("target", ent)), ent, Filter.Pvs(ent), true);
}

private void OnMobStateChange(Entity<RevenantAnimatedComponent> ent, ref MobStateChangedEvent args)
Expand Down
205 changes: 205 additions & 0 deletions Content.Server/Revenant/EntitySystems/RevenantStasisSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using Content.Server.Bible;
using Content.Server.Bible.Components;
using Content.Server.Construction;
using Content.Server.Explosion.EntitySystems;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Kitchen.EntitySystems;
using Content.Server.Mind;
using Content.Server.Revenant.Components;
using Content.Server.VoiceMask;
using Content.Shared.Alert;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Movement.Components;
using Content.Shared.Popups;
using Content.Shared.Revenant;
using Content.Shared.Speech;
using Content.Shared.StatusEffect;
using Content.Shared.Tag;
using Robust.Shared.Player;

namespace Content.Server.Revenant.EntitySystems;

public sealed partial class RevenantStasisSystem : EntitySystem
{
[Dependency] private readonly StatusEffectsSystem _statusEffects = default!;
[Dependency] private readonly MindSystem _mind = default!;
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
[Dependency] private readonly GhostRoleSystem _ghostRoles = default!;
[Dependency] private readonly MetaDataSystem _meta = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly TagSystem _tags = default!;
[Dependency] private readonly ExplosionSystem _explosion = default!;

[ValidatePrototypeId<StatusEffectPrototype>]
private const string RevenantStasisId = "Stasis";

public override void Initialize()
{
base.Initialize();

SubscribeLocalEvent<RevenantStasisComponent, ComponentStartup>(OnStartup);
SubscribeLocalEvent<RevenantStasisComponent, ComponentShutdown>(OnShutdown);
SubscribeLocalEvent<RevenantStasisComponent, StatusEffectEndedEvent>(OnStatusEnded);
SubscribeLocalEvent<RevenantStasisComponent, ChangeDirectionAttemptEvent>(OnAttemptDirection);
SubscribeLocalEvent<RevenantStasisComponent, ExaminedEvent>(OnExamine);
SubscribeLocalEvent<RevenantStasisComponent, ConstructionConsumedObjectEvent>(OnCrafted);
SubscribeLocalEvent<RevenantStasisComponent, GrindAttemptEvent>(OnGrindAttempt);

SubscribeLocalEvent<RevenantStasisComponent, AfterInteractUsingEvent>(OnBibleInteract, before: [typeof(BibleSystem)]);
SubscribeLocalEvent<RevenantStasisComponent, ExorciseRevenantDoAfterEvent>(OnExorcise);
}

private void OnStartup(EntityUid uid, RevenantStasisComponent component, ComponentStartup args)
{
EnsureComp<AlertsComponent>(uid);

EnsureComp<StatusEffectsComponent>(uid);
_statusEffects.TryAddStatusEffect(uid, RevenantStasisId, component.StasisDuration, true);

var mover = EnsureComp<InputMoverComponent>(uid);
mover.CanMove = false;
Dirty(uid, mover);

var speech = EnsureComp<SpeechComponent>(uid);
speech.SpeechVerb = "Ghost";
Dirty(uid, speech);

var voice = EnsureComp<VoiceMaskComponent>(uid);
voice.VoiceName = Comp<MetaDataComponent>(component.Revenant).EntityName;

if (TryComp<GhostRoleComponent>(uid, out var ghostRole))
_ghostRoles.UnregisterGhostRole((uid, ghostRole));
}

private void OnShutdown(EntityUid uid, RevenantStasisComponent component, ComponentShutdown args)
{
if (_statusEffects.HasStatusEffect(uid, RevenantStasisId))
{
if (_mind.TryGetMind(uid, out var mindId, out var _))
_mind.TransferTo(mindId, null);
QueueDel(component.Revenant);
}
}

private void OnStatusEnded(EntityUid uid, RevenantStasisComponent component, StatusEffectEndedEvent args)
{
if (args.Key == "Stasis")
{
_transformSystem.SetCoordinates(component.Revenant, Transform(uid).Coordinates);
_transformSystem.AttachToGridOrMap(component.Revenant);
_meta.SetEntityPaused(component.Revenant, false);
if (_mind.TryGetMind(uid, out var mindId, out var _))
_mind.TransferTo(mindId, component.Revenant);
QueueDel(uid);
}
}

private void OnExamine(Entity<RevenantStasisComponent> entity, ref ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("revenant-stasis-regenerating"));
}

private void OnCrafted(EntityUid uid, RevenantStasisComponent comp, ConstructionConsumedObjectEvent args)
{
// Permanently sealed into revenant plushie

var speech = EnsureComp<SpeechComponent>(args.New);
speech.SpeechVerb = "Ghost";
Dirty(args.New, speech);

EnsureComp<InputMoverComponent>(args.New);

var voice = EnsureComp<VoiceMaskComponent>(args.New);
voice.VoiceName = Comp<MetaDataComponent>(comp.Revenant).EntityName;

if (_mind.TryGetMind(uid, out var mindId, out var _))
_mind.TransferTo(mindId, args.New);
}

private void OnGrindAttempt(EntityUid uid, RevenantStasisComponent comp, GrindAttemptEvent args)
{
foreach (var reagent in args.Reagents)
{
if (_tags.HasAnyTag(reagent, "Salt", "Holy"))
return;
}

// Ripped off the changeling blood explosion variables
_explosion.QueueExplosion(
args.Grinder.Owner,
"Default",
7.5f, // totalIntensity
4f, // slope
2f // maxTileIntensity
);

args.Cancel();
}

private void OnAttemptDirection(EntityUid uid, RevenantStasisComponent comp, ChangeDirectionAttemptEvent args)
{
args.Cancel();
}

private void OnBibleInteract(EntityUid uid, RevenantStasisComponent comp, ref AfterInteractUsingEvent args)
{
if (args.Handled)
return;
if (args.Target == null)
return;
var bible = args.Used;
var target = args.Target.Value;
var user = args.User;
if (!HasComp<BibleComponent>(args.Used))
return;

if (!TryComp<RevenantStasisComponent>(target, out var stasis))
return;

var revenant = stasis.Revenant;

if (revenant.Comp.ExorcismRequiresBibleUser && !HasComp<BibleUserComponent>(args.User))
{
_popup.PopupEntity(Loc.GetString("revenant-exorcise-fail", ("bible", bible)), user, user);
return;
}

var doAfterEventArgs = new DoAfterArgs(EntityManager, user, TimeSpan.FromSeconds(10), new ExorciseRevenantDoAfterEvent(), target, target, bible)
{
BreakOnMove = true,
BreakOnWeightlessMove = false,
BreakOnDamage = true,
NeedHand = true,
DistanceThreshold = 1f
};

if (!_doAfter.TryStartDoAfter(doAfterEventArgs))
return;

args.Handled = true;

_popup.PopupEntity(Loc.GetString("revenant-exorcise-begin-user", [("bible", bible), ("user", user), ("revenant", revenant.Owner)]), user, user);
_popup.PopupEntity(Loc.GetString("revenant-exorcise-begin-target", [("bible", bible), ("user", user), ("revenant", revenant.Owner)]), target, target, PopupType.MediumCaution);
_popup.PopupEntity(Loc.GetString("revenant-exorcise-begin-other", [("bible", bible), ("user", user), ("revenant", revenant.Owner)]), target, Filter.Pvs(target).RemovePlayersByAttachedEntity([user, target]), true);
}

private void OnExorcise(EntityUid uid, RevenantStasisComponent comp, ExorciseRevenantDoAfterEvent args)
{
if (args.Cancelled)
return;
if (args.Target == null || args.Used == null)
return;

var target = args.Target.Value;
var used = args.Used.Value;

_popup.PopupEntity(Loc.GetString("revenant-exorcise-success", [("bible", used), ("user", args.User), ("revenant", comp.Revenant.Owner)]), target);

RemComp<RevenantStasisComponent>(args.Target.Value);
}
}
Loading
Loading