Skip to content

Commit

Permalink
Merge pull request #262 from TGRCdev/revenant-stasis
Browse files Browse the repository at this point in the history
Revenants enter stasis on death
  • Loading branch information
formlessnameless authored Sep 16, 2024
2 parents 4ca8a6a + c38b30a commit f1ae49c
Show file tree
Hide file tree
Showing 21 changed files with 389 additions and 17 deletions.
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

0 comments on commit f1ae49c

Please sign in to comment.