358 lines
15 KiB
C#
358 lines
15 KiB
C#
using Content.Server.Actions;
|
|
using Content.Server.DoAfter;
|
|
using Content.Server.Hands.Components;
|
|
using Content.Server.Inventory.Components;
|
|
using Content.Server.Popups;
|
|
using Content.Shared.Actions;
|
|
using Content.Shared.Actions.Components;
|
|
using Content.Shared.Audio;
|
|
using Content.Shared.Damage;
|
|
using Content.Shared.Examine;
|
|
using Content.Shared.Interaction;
|
|
using Content.Shared.MobState;
|
|
using Content.Shared.MobState.EntitySystems;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Audio;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Localization;
|
|
using Robust.Shared.Log;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Content.Server.Guardian
|
|
{
|
|
/// <summary>
|
|
/// A guardian has a host it's attached to that it fights for. A fighting spirit.
|
|
/// </summary>
|
|
public sealed class GuardianSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
|
|
[Dependency] private readonly PopupSystem _popupSystem = default!;
|
|
[Dependency] private readonly DamageableSystem _damageSystem = default!;
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
SubscribeLocalEvent<GuardianCreatorComponent, UseInHandEvent>(OnCreatorUse);
|
|
SubscribeLocalEvent<GuardianCreatorComponent, AfterInteractEvent>(OnCreatorInteract);
|
|
SubscribeLocalEvent<GuardianCreatorComponent, ExaminedEvent>(OnCreatorExamine);
|
|
SubscribeLocalEvent<GuardianCreatorInjectedEvent>(OnCreatorInject);
|
|
SubscribeLocalEvent<GuardianCreatorInjectCancelledEvent>(OnCreatorCancelled);
|
|
|
|
SubscribeLocalEvent<GuardianComponent, MoveEvent>(OnGuardianMove);
|
|
SubscribeLocalEvent<GuardianComponent, DamageChangedEvent>(OnGuardianDamaged);
|
|
SubscribeLocalEvent<GuardianComponent, PlayerAttachedEvent>(OnGuardianPlayer);
|
|
SubscribeLocalEvent<GuardianComponent, PlayerDetachedEvent>(OnGuardianUnplayer);
|
|
|
|
SubscribeLocalEvent<GuardianHostComponent, ComponentInit>(OnHostInit);
|
|
SubscribeLocalEvent<GuardianHostComponent, MoveEvent>(OnHostMove);
|
|
SubscribeLocalEvent<GuardianHostComponent, MobStateChangedEvent>(OnHostStateChange);
|
|
SubscribeLocalEvent<GuardianHostComponent, ComponentShutdown>(OnHostShutdown);
|
|
}
|
|
|
|
private void OnGuardianUnplayer(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args)
|
|
{
|
|
var host = component.Host;
|
|
|
|
if (!TryComp<GuardianHostComponent>(host, out var hostComponent)) return;
|
|
|
|
RetractGuardian(hostComponent, component);
|
|
}
|
|
|
|
private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args)
|
|
{
|
|
var host = component.Host;
|
|
|
|
if (!HasComp<GuardianHostComponent>(host)) return;
|
|
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-available"), host, Filter.Entities(host));
|
|
}
|
|
|
|
private void OnHostInit(EntityUid uid, GuardianHostComponent component, ComponentInit args)
|
|
{
|
|
component.GuardianContainer = uid.EnsureContainer<ContainerSlot>("GuardianContainer");
|
|
}
|
|
|
|
private void OnHostShutdown(EntityUid uid, GuardianHostComponent component, ComponentShutdown args)
|
|
{
|
|
if (component.HostedGuardian == null) return;
|
|
EntityManager.QueueDeleteEntity(component.HostedGuardian.Value);
|
|
}
|
|
|
|
public void ToggleGuardian(GuardianHostComponent hostComponent)
|
|
{
|
|
if (hostComponent.HostedGuardian == null ||
|
|
!TryComp(hostComponent.HostedGuardian, out GuardianComponent? guardianComponent)) return;
|
|
|
|
if (guardianComponent.GuardianLoose)
|
|
{
|
|
RetractGuardian(hostComponent, guardianComponent);
|
|
}
|
|
else
|
|
{
|
|
ReleaseGuardian(hostComponent, guardianComponent);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds the guardian host component to the user and spawns the guardian inside said component
|
|
/// </summary>
|
|
private void OnCreatorUse(EntityUid uid, GuardianCreatorComponent component, UseInHandEvent args)
|
|
{
|
|
if (args.Handled) return;
|
|
args.Handled = true;
|
|
UseCreator(args.User, args.User, component);
|
|
}
|
|
|
|
private void OnCreatorInteract(EntityUid uid, GuardianCreatorComponent component, AfterInteractEvent args)
|
|
{
|
|
if (args.Handled || args.Target == null) return;
|
|
args.Handled = true;
|
|
UseCreator(args.User, args.Target.Value, component);
|
|
}
|
|
|
|
private void OnCreatorCancelled(GuardianCreatorInjectCancelledEvent ev)
|
|
{
|
|
ev.Component.Injecting = false;
|
|
}
|
|
|
|
private void UseCreator(EntityUid user, EntityUid target, GuardianCreatorComponent component)
|
|
{
|
|
if (component.Used)
|
|
{
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-activator-empty-invalid-creation"), user, Filter.Entities(user));
|
|
return;
|
|
}
|
|
|
|
// Can only inject things with the component...
|
|
if (!HasComp<CanHostGuardianComponent>(target))
|
|
{
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-activator-invalid-target"), user, Filter.Entities(user));
|
|
return;
|
|
}
|
|
|
|
|
|
// If user is already a host don't duplicate.
|
|
if (HasComp<GuardianHostComponent>(target))
|
|
{
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, Filter.Entities(user));
|
|
return;
|
|
}
|
|
|
|
// Can't work without actions
|
|
EntityManager.EnsureComponent<ServerActionsComponent>(target);
|
|
|
|
if (component.Injecting) return;
|
|
|
|
component.Injecting = true;
|
|
|
|
_doAfterSystem.DoAfter(new DoAfterEventArgs(user, component.InjectionDelay, target: target)
|
|
{
|
|
BroadcastFinishedEvent = new GuardianCreatorInjectedEvent(user, target, component),
|
|
BroadcastCancelledEvent = new GuardianCreatorInjectCancelledEvent(target, component),
|
|
BreakOnTargetMove = true,
|
|
BreakOnUserMove = true,
|
|
});
|
|
}
|
|
|
|
private void OnCreatorInject(GuardianCreatorInjectedEvent ev)
|
|
{
|
|
var comp = ev.Component;
|
|
|
|
if (comp.Deleted ||
|
|
comp.Used ||
|
|
!TryComp<HandsComponent>(ev.User, out var hands) ||
|
|
!hands.IsHolding(comp.Owner) ||
|
|
HasComp<GuardianHostComponent>(ev.Target) ||
|
|
!TryComp<SharedActionsComponent>(ev.Target, out var actions))
|
|
{
|
|
comp.Injecting = false;
|
|
return;
|
|
}
|
|
|
|
var hostXform = EntityManager.GetComponent<TransformComponent>(ev.Target);
|
|
var host = EntityManager.EnsureComponent<GuardianHostComponent>(ev.Target);
|
|
// Use map position so it's not inadvertantly parented to the host + if it's in a container it spawns outside I guess.
|
|
var guardian = EntityManager.SpawnEntity(comp.GuardianProto, hostXform.MapPosition);
|
|
|
|
host.GuardianContainer.Insert(guardian);
|
|
host.HostedGuardian = guardian;
|
|
|
|
if (TryComp(guardian, out GuardianComponent? guardianComponent))
|
|
{
|
|
guardianComponent.Host = ev.Target;
|
|
|
|
// Grant the user the recall action and notify them
|
|
actions.Grant(ActionType.ManifestGuardian);
|
|
SoundSystem.Play(Filter.Entities(ev.Target), "/Audio/Effects/guardian_inject.ogg", ev.Target);
|
|
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-created"), ev.Target, Filter.Entities(ev.Target));
|
|
// Exhaust the activator
|
|
comp.Used = true;
|
|
}
|
|
else
|
|
{
|
|
Logger.ErrorS("guardian", $"Tried to spawn a guardian that doesn't have {nameof(GuardianComponent)}");
|
|
EntityManager.QueueDeleteEntity(guardian);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers when the host receives damage which puts the host in either critical or killed state
|
|
/// </summary>
|
|
private void OnHostStateChange(EntityUid uid, GuardianHostComponent component, MobStateChangedEvent args)
|
|
{
|
|
if (component.HostedGuardian == null) return;
|
|
|
|
if (args.CurrentMobState.IsCritical())
|
|
{
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-critical-warn"), component.HostedGuardian.Value, Filter.Entities(component.HostedGuardian.Value));
|
|
SoundSystem.Play(Filter.Entities(component.HostedGuardian.Value), "/Audio/Effects/guardian_warn.ogg", component.HostedGuardian.Value);
|
|
}
|
|
else if (args.CurrentMobState.IsDead())
|
|
{
|
|
SoundSystem.Play(Filter.Pvs(uid), "/Audio/Voice/Human/malescream_guardian.ogg", uid, AudioHelpers.WithVariation(0.20f));
|
|
EntityManager.RemoveComponent<GuardianHostComponent>(uid);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Handles guardian receiving damage and splitting it with the host according to his defence percent
|
|
/// </summary>
|
|
private void OnGuardianDamaged(EntityUid uid, GuardianComponent component, DamageChangedEvent args)
|
|
{
|
|
if (args.DamageDelta == null) return;
|
|
|
|
_damageSystem.TryChangeDamage(component.Host, args.DamageDelta * component.DamageShare);
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-taking-damage"), component.Host, Filter.Entities(component.Host));
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Triggers while trying to examine an activator to see if it's used
|
|
/// </summary>
|
|
private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args)
|
|
{
|
|
if (component.Used)
|
|
{
|
|
args.PushMarkup(Loc.GetString("guardian-activator-empty-examine"));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every time the host moves, to make sure the distance between the host and the guardian isn't too far
|
|
/// </summary>
|
|
private void OnHostMove(EntityUid uid, GuardianHostComponent component, ref MoveEvent args)
|
|
{
|
|
if (component.HostedGuardian == null ||
|
|
!TryComp(component.HostedGuardian, out GuardianComponent? guardianComponent) ||
|
|
!guardianComponent.GuardianLoose) return;
|
|
|
|
CheckGuardianMove(uid, component.HostedGuardian.Value, component);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called every time the guardian moves: makes sure it's not out of it's allowed distance
|
|
/// </summary>
|
|
private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args)
|
|
{
|
|
if (!component.GuardianLoose) return;
|
|
|
|
CheckGuardianMove(component.Host, uid, guardianComponent: component);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Retract the guardian if either the host or the guardian move away from each other.
|
|
/// </summary>
|
|
private void CheckGuardianMove(
|
|
EntityUid hostUid,
|
|
EntityUid guardianUid,
|
|
GuardianHostComponent? hostComponent = null,
|
|
GuardianComponent? guardianComponent = null,
|
|
TransformComponent? hostXform = null,
|
|
TransformComponent? guardianXform = null)
|
|
{
|
|
if (!Resolve(hostUid, ref hostComponent, ref hostXform) ||
|
|
!Resolve(guardianUid, ref guardianComponent, ref guardianXform))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!guardianComponent.GuardianLoose) return;
|
|
|
|
if (!guardianXform.Coordinates.InRange(EntityManager, hostXform.Coordinates, guardianComponent.DistanceAllowed))
|
|
{
|
|
RetractGuardian(hostComponent, guardianComponent);
|
|
}
|
|
}
|
|
|
|
private bool CanRelease(GuardianHostComponent host, GuardianComponent guardian)
|
|
{
|
|
return HasComp<ActorComponent>(guardian.Owner);
|
|
}
|
|
|
|
private void ReleaseGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
|
|
{
|
|
if (guardianComponent.GuardianLoose)
|
|
{
|
|
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
|
return;
|
|
}
|
|
|
|
if (!CanRelease(hostComponent, guardianComponent))
|
|
{
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-no-soul"), hostComponent.Owner, Filter.Entities(hostComponent.Owner));
|
|
return;
|
|
}
|
|
|
|
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
|
hostComponent.GuardianContainer.Remove(guardianComponent.Owner);
|
|
DebugTools.Assert(!hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
|
|
|
guardianComponent.GuardianLoose = true;
|
|
}
|
|
|
|
private void RetractGuardian(GuardianHostComponent hostComponent, GuardianComponent guardianComponent)
|
|
{
|
|
if (!guardianComponent.GuardianLoose)
|
|
{
|
|
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
|
return;
|
|
}
|
|
|
|
hostComponent.GuardianContainer.Insert(guardianComponent.Owner);
|
|
DebugTools.Assert(hostComponent.GuardianContainer.Contains(guardianComponent.Owner));
|
|
_popupSystem.PopupEntity(Loc.GetString("guardian-entity-recall"), hostComponent.Owner, Filter.Pvs(hostComponent.Owner));
|
|
guardianComponent.GuardianLoose = false;
|
|
}
|
|
|
|
private sealed class GuardianCreatorInjectedEvent : EntityEventArgs
|
|
{
|
|
public EntityUid User { get; }
|
|
public EntityUid Target { get; }
|
|
public GuardianCreatorComponent Component { get; }
|
|
|
|
public GuardianCreatorInjectedEvent(EntityUid user, EntityUid target, GuardianCreatorComponent component)
|
|
{
|
|
User = user;
|
|
Target = target;
|
|
Component = component;
|
|
}
|
|
}
|
|
|
|
private sealed class GuardianCreatorInjectCancelledEvent : EntityEventArgs
|
|
{
|
|
public EntityUid Target { get; }
|
|
public GuardianCreatorComponent Component { get; }
|
|
|
|
public GuardianCreatorInjectCancelledEvent(EntityUid target, GuardianCreatorComponent component)
|
|
{
|
|
Target = target;
|
|
Component = component;
|
|
}
|
|
}
|
|
}
|
|
}
|