mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 18:31:17 +03:00
* guardian popup fixes * actually use the entityqueries * finish a thought challenge - difficulty impossible * curse of fildrance * can't believe he's done this
432 lines
15 KiB
C#
432 lines
15 KiB
C#
using Content.Shared.Actions;
|
|
using Content.Shared.Damage.Systems;
|
|
using Content.Shared.DoAfter;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.Gibbing;
|
|
using Content.Shared.Guardian.Components;
|
|
using Content.Shared.Hands.Components;
|
|
using Content.Shared.Hands.EntitySystems;
|
|
using Content.Shared.IdentityManagement;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.Interaction.Events;
|
|
using Content.Shared.Mech.EntitySystems;
|
|
using Content.Shared.Mobs;
|
|
using Content.Shared.Popups;
|
|
using Robust.Shared.Audio.Systems;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Shared.Guardian;
|
|
|
|
/// <summary>
|
|
/// A guardian has a host it's attached to that it fights for. A fighting spirit.
|
|
/// </summary>
|
|
public sealed partial class GuardianSystem : EntitySystem
|
|
{
|
|
[Dependency] private DamageableSystem _damage = default!;
|
|
[Dependency] private GibbingSystem _gibbing = default!;
|
|
[Dependency] private IGameTiming _timing = default!;
|
|
[Dependency] private SharedActionsSystem _action = default!;
|
|
[Dependency] private SharedAudioSystem _audio = default!;
|
|
[Dependency] private SharedContainerSystem _container = default!;
|
|
[Dependency] private SharedDoAfterSystem _doAfter = default!;
|
|
[Dependency] private SharedHandsSystem _hands = default!;
|
|
[Dependency] private SharedPopupSystem _popup = default!;
|
|
[Dependency] private SharedTransformSystem _transform = default!;
|
|
|
|
[Dependency] private EntityQuery<GuardianComponent> _guardianQuery;
|
|
[Dependency] private EntityQuery<GuardianHostComponent> _guardianHostQuery;
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnGuardianShutdown(Entity<GuardianComponent> ent, ref ComponentShutdown args)
|
|
{
|
|
if (!_guardianHostQuery.TryComp(ent, out GuardianHostComponent? hostComponent))
|
|
return;
|
|
|
|
_container.Remove(ent.Owner, hostComponent.GuardianContainer);
|
|
hostComponent.HostedGuardian = null;
|
|
PredictedDel(hostComponent.ActionEntity);
|
|
hostComponent.ActionEntity = null;
|
|
ent.Comp.Host = null;
|
|
|
|
Dirty(ent);
|
|
Dirty(ent, hostComponent);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnPerformAction(Entity<GuardianHostComponent> ent, ref GuardianToggleActionEvent args)
|
|
{
|
|
if (args.Handled)
|
|
return;
|
|
|
|
if (_container.IsEntityInContainer(ent.Owner))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("guardian-inside-container"), ent.Owner, ent.Owner);
|
|
return;
|
|
}
|
|
|
|
if (ent.Comp.HostedGuardian != null)
|
|
ToggleGuardian(ent);
|
|
|
|
args.Handled = true;
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnGuardianPlayerDetached(Entity<GuardianComponent> ent, ref PlayerDetachedEvent args)
|
|
{
|
|
if (!_guardianHostQuery.TryComp(ent.Comp.Host, out GuardianHostComponent? hostComponent) ||
|
|
TerminatingOrDeleted(ent.Owner))
|
|
{
|
|
PredictedQueueDel(ent.Owner);
|
|
ent.Comp.Host = null;
|
|
Dirty(ent);
|
|
return;
|
|
}
|
|
|
|
RetractGuardian((ent.Comp.Host.Value, hostComponent), (ent.Owner, ent.Comp));
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnGuardianPlayerAttached(Entity<GuardianComponent> ent, ref PlayerAttachedEvent args)
|
|
{
|
|
var host = ent.Comp.Host;
|
|
if (!_guardianHostQuery.HasComp(host))
|
|
{
|
|
PredictedQueueDel(ent.Owner);
|
|
ent.Comp.Host = null;
|
|
Dirty(ent);
|
|
return;
|
|
}
|
|
|
|
_popup.PopupEntity(Loc.GetString("guardian-available"), host.Value, host.Value);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnHostInit(Entity<GuardianHostComponent> ent, ref ComponentInit args)
|
|
{
|
|
ent.Comp.GuardianContainer = _container.EnsureContainer<ContainerSlot>(ent.Owner, "GuardianContainer");
|
|
_action.AddAction(ent.Owner, ref ent.Comp.ActionEntity, ent.Comp.Action);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnHostShutdown(Entity<GuardianHostComponent> ent, ref ComponentShutdown args)
|
|
{
|
|
if (ent.Comp.HostedGuardian is not { } guardian)
|
|
return;
|
|
|
|
// Ensure held items are dropped before deleting guardian.
|
|
if (HasComp<HandsComponent>(guardian))
|
|
_gibbing.Gib(ent.Comp.HostedGuardian.Value);
|
|
|
|
PredictedQueueDel(guardian);
|
|
ent.Comp.HostedGuardian = null;
|
|
PredictedDel(ent.Comp.ActionEntity);
|
|
ent.Comp.ActionEntity = null;
|
|
Dirty(ent);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnGuardianAttackAttempt(Entity<GuardianComponent> ent, ref AttackAttemptEvent args)
|
|
{
|
|
if (args.Cancelled || args.Target != ent.Comp.Host)
|
|
return;
|
|
|
|
if (_timing.CurTime >= ent.Comp.LastAttackPopupTime + ent.Comp.AttackPopupDelay)
|
|
{
|
|
_popup.PopupCursor(Loc.GetString("guardian-attack-host"), ent.Owner, PopupType.LargeCaution);
|
|
}
|
|
ent.Comp.LastAttackPopupTime = _timing.CurTime;
|
|
|
|
args.Cancel();
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnPilotAttackAttempt(Entity<GuardianHostComponent> ent,
|
|
ref MechPilotRelayedEvent<GettingAttackedAttemptEvent> args)
|
|
{
|
|
if (args.Args.Cancelled || args.Args.Attacker != ent.Comp.HostedGuardian)
|
|
return;
|
|
|
|
if (_guardianQuery.TryComp(args.Args.Attacker, out var guardian))
|
|
{
|
|
if (_timing.CurTime >= guardian.LastAttackPopupTime + guardian.AttackPopupDelay)
|
|
{
|
|
_popup.PopupCursor(Loc.GetString("guardian-attack-host"),
|
|
args.Args.Attacker,
|
|
PopupType.LargeCaution);
|
|
}
|
|
guardian.LastAttackPopupTime = _timing.CurTime;
|
|
}
|
|
|
|
args.Args.Cancelled = true;
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void ToggleGuardian(Entity<GuardianHostComponent> ent)
|
|
{
|
|
if (!_guardianQuery.TryComp(ent.Comp.HostedGuardian, out var guardianComponent))
|
|
return;
|
|
|
|
if (guardianComponent.GuardianLoose)
|
|
RetractGuardian(ent, (ent.Comp.HostedGuardian.Value, guardianComponent));
|
|
else
|
|
ReleaseGuardian(ent, (ent.Comp.HostedGuardian.Value, guardianComponent));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the guardian host component to the user and spawns the guardian inside said component.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnCreatorUse(Entity<GuardianCreatorComponent> ent, ref UseInHandEvent args)
|
|
{
|
|
if (args.Handled)
|
|
return;
|
|
|
|
args.Handled = true;
|
|
UseCreator(args.User, args.User, ent);
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnCreatorInteract(Entity<GuardianCreatorComponent> ent, ref AfterInteractEvent args)
|
|
{
|
|
if (args.Handled || args.Target == null || !args.CanReach)
|
|
return;
|
|
|
|
args.Handled = true;
|
|
UseCreator(args.User, args.Target.Value, ent);
|
|
}
|
|
|
|
private void UseCreator(EntityUid user, EntityUid target, Entity<GuardianCreatorComponent> ent)
|
|
{
|
|
if (ent.Comp.Used)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString(ent.Comp.EmptyPopup), user, user);
|
|
|
|
return;
|
|
}
|
|
|
|
// Can only inject things with the component...
|
|
if (!HasComp<CanHostGuardianComponent>(target))
|
|
{
|
|
var msg = Loc.GetString("guardian-activator-invalid-target",
|
|
("entity", Identity.Entity(target, EntityManager, user)));
|
|
_popup.PopupEntity(msg, user, user);
|
|
return;
|
|
}
|
|
|
|
// If user is already a host don't duplicate.
|
|
if (_guardianHostQuery.HasComp(target))
|
|
{
|
|
_popup.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, user);
|
|
return;
|
|
}
|
|
|
|
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager,
|
|
user,
|
|
ent.Comp.InjectionDelay,
|
|
new GuardianCreatorDoAfterEvent(),
|
|
ent,
|
|
target: target,
|
|
used: ent)
|
|
{
|
|
BreakOnMove = true,
|
|
NeedHand = true,
|
|
BreakOnHandChange = true
|
|
});
|
|
}
|
|
|
|
[SubscribeLocalEvent]
|
|
private void OnDoAfter(Entity<GuardianCreatorComponent> ent, ref GuardianCreatorDoAfterEvent args)
|
|
{
|
|
if (args.Handled || args.Args.Target == null || args.Cancelled || ent.Comp.Deleted || ent.Comp.Used)
|
|
return;
|
|
|
|
if (!_hands.IsHolding(args.Args.User, ent.Owner) || _guardianHostQuery.HasComp(args.Args.Target))
|
|
return;
|
|
|
|
var hostXform = Transform(args.Args.Target.Value);
|
|
var host = EnsureComp<GuardianHostComponent>(args.Args.Target.Value);
|
|
|
|
// Use map position so it's not inadvertently parented to the host + if it's in a container it spawns outside I guess.
|
|
var guardian = PredictedSpawnAtPosition(ent.Comp.GuardianProto,
|
|
_transform.GetMoverCoordinates(args.Args.Target.Value, xform: hostXform));
|
|
_container.Insert(guardian, host.GuardianContainer);
|
|
host.HostedGuardian = guardian;
|
|
|
|
if (_guardianQuery.TryComp(guardian, out GuardianComponent? guardianComp))
|
|
{
|
|
guardianComp.Host = args.Args.Target.Value;
|
|
Dirty(guardian, guardianComp);
|
|
_audio.PlayPredicted(ent.Comp.UsedSound,
|
|
ent.Owner,
|
|
args.Args.Target);
|
|
_popup.PopupEntity(Loc.GetString(ent.Comp.GuardianHauntedPopup),
|
|
args.Args.Target.Value,
|
|
args.Args.Target.Value);
|
|
// Exhaust the activator
|
|
ent.Comp.Used = true;
|
|
}
|
|
else
|
|
{
|
|
Log.Error($"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
|
|
PredictedDel(guardian);
|
|
}
|
|
|
|
Dirty(ent);
|
|
args.Handled = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers when the host receives damage which puts the host in either critical or killed state.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnHostStateChange(Entity<GuardianHostComponent> ent, ref MobStateChangedEvent args)
|
|
{
|
|
if (ent.Comp.HostedGuardian == null ||
|
|
!_guardianQuery.TryComp(ent.Comp.HostedGuardian, out GuardianComponent? guardianComp))
|
|
return;
|
|
|
|
if (args.NewMobState == MobState.Critical)
|
|
{
|
|
_popup.PopupEntity(Loc.GetString(ent.Comp.GuardianHostCritWarn),
|
|
ent.Comp.HostedGuardian.Value,
|
|
ent.Comp.HostedGuardian.Value);
|
|
_audio.PlayPredicted(guardianComp.CriticalSound, ent.Comp.HostedGuardian.Value, args.Target);
|
|
}
|
|
else if (args.NewMobState == MobState.Dead)
|
|
{
|
|
_audio.PlayPredicted(guardianComp.DeathSound, ent.Owner, args.Target);
|
|
RemComp<GuardianHostComponent>(ent.Owner);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles guardian receiving damage and splitting it with the host according to his defense percent.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnGuardianDamaged(Entity<GuardianComponent> ent, ref DamageDealtEvent args)
|
|
{
|
|
if (_timing.ApplyingState)
|
|
return;
|
|
|
|
if (args.Damage == null! || ent.Comp.Host == null || ent.Comp.DamageShare == 0)
|
|
return;
|
|
|
|
_damage.ChangeDamage(
|
|
ent.Comp.Host.Value,
|
|
args.Damage * ent.Comp.DamageShare,
|
|
origin: args.Origin,
|
|
ignoreResistances: true,
|
|
interruptsDoAfters: false);
|
|
_popup.PopupEntity(Loc.GetString(ent.Comp.GuardianDamagePopup), ent.Comp.Host.Value, ent.Comp.Host.Value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers while trying to examine an activator to see if it's used.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnCreatorExamine(Entity<GuardianCreatorComponent> ent, ref ExaminedEvent args)
|
|
{
|
|
if (!ent.Comp.Used)
|
|
return;
|
|
|
|
args.PushMarkup(Loc.GetString(ent.Comp.EmptyExamine));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every time the host moves, to make sure the host and the guardian are not too far away from each other.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnHostMove(Entity<GuardianHostComponent> ent, ref MoveEvent args)
|
|
{
|
|
if (!_guardianQuery.TryComp(ent.Comp.HostedGuardian, out GuardianComponent? guardianComponent) ||
|
|
!guardianComponent.GuardianLoose)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CheckGuardianMove(ent.Owner, ent.Comp.HostedGuardian.Value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every time the guardian moves: makes sure it's not out of it's allowed distance.
|
|
/// </summary>
|
|
[SubscribeLocalEvent]
|
|
private void OnGuardianMove(Entity<GuardianComponent> ent, ref MoveEvent args)
|
|
{
|
|
if (!ent.Comp.GuardianLoose)
|
|
return;
|
|
|
|
if (ent.Comp.Host == null)
|
|
return;
|
|
|
|
CheckGuardianMove(ent.Comp.Host.Value, ent.Owner);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retract the guardian if either the host or the guardian move away from each other.
|
|
/// </summary>
|
|
private void CheckGuardianMove(
|
|
Entity<GuardianHostComponent?> host,
|
|
Entity<GuardianComponent?> guardian,
|
|
TransformComponent? hostXform = null,
|
|
TransformComponent? guardianXform = null)
|
|
{
|
|
if (TerminatingOrDeleted(guardian.Owner) || TerminatingOrDeleted(host.Owner))
|
|
return;
|
|
|
|
if (!Resolve(host.Owner, ref host.Comp, ref hostXform) ||
|
|
!Resolve(guardian.Owner, ref guardian.Comp, ref guardianXform))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!guardian.Comp.GuardianLoose)
|
|
return;
|
|
|
|
if (!_transform.InRange(guardianXform.Coordinates, hostXform.Coordinates, guardian.Comp.DistanceAllowed))
|
|
RetractGuardian((host.Owner, host.Comp), guardian);
|
|
}
|
|
|
|
private void ReleaseGuardian(Entity<GuardianHostComponent> host, Entity<GuardianComponent> guardian)
|
|
{
|
|
_audio.PlayPredicted(host.Comp.ReleaseSound, guardian, host);
|
|
if (guardian.Comp.GuardianLoose)
|
|
{
|
|
DebugTools.Assert(!host.Comp.GuardianContainer.Contains(guardian));
|
|
return;
|
|
}
|
|
|
|
DebugTools.Assert(host.Comp.GuardianContainer.Contains(guardian));
|
|
_container.Remove(guardian.Owner, host.Comp.GuardianContainer);
|
|
DebugTools.Assert(!host.Comp.GuardianContainer.Contains(guardian));
|
|
|
|
guardian.Comp.GuardianLoose = true;
|
|
Dirty(host);
|
|
Dirty(guardian);
|
|
}
|
|
|
|
private void RetractGuardian(Entity<GuardianHostComponent> host, Entity<GuardianComponent?> guardian)
|
|
{
|
|
if (_timing.ApplyingState)
|
|
return;
|
|
|
|
if (!Resolve(guardian, ref guardian.Comp))
|
|
return;
|
|
|
|
if (!guardian.Comp.GuardianLoose)
|
|
{
|
|
DebugTools.Assert(host.Comp.GuardianContainer.Contains(guardian));
|
|
return;
|
|
}
|
|
|
|
_container.Insert(guardian.Owner, host.Comp.GuardianContainer);
|
|
_popup.PopupEntity(Loc.GetString(host.Comp.GuardianHostRecall), host.Owner, host.Owner);
|
|
guardian.Comp.GuardianLoose = false;
|
|
|
|
Dirty(host);
|
|
Dirty(guardian);
|
|
}
|
|
}
|