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 { /// /// A guardian has a host it's attached to that it fights for. A fighting spirit. /// 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(OnCreatorUse); SubscribeLocalEvent(OnCreatorInteract); SubscribeLocalEvent(OnCreatorExamine); SubscribeLocalEvent(OnCreatorInject); SubscribeLocalEvent(OnCreatorCancelled); SubscribeLocalEvent(OnGuardianMove); SubscribeLocalEvent(OnGuardianDamaged); SubscribeLocalEvent(OnGuardianPlayer); SubscribeLocalEvent(OnGuardianUnplayer); SubscribeLocalEvent(OnHostInit); SubscribeLocalEvent(OnHostMove); SubscribeLocalEvent(OnHostStateChange); SubscribeLocalEvent(OnHostShutdown); } private void OnGuardianUnplayer(EntityUid uid, GuardianComponent component, PlayerDetachedEvent args) { var host = component.Host; if (!TryComp(host, out var hostComponent)) return; RetractGuardian(hostComponent, component); } private void OnGuardianPlayer(EntityUid uid, GuardianComponent component, PlayerAttachedEvent args) { var host = component.Host; if (!HasComp(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("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); } } /// /// Adds the guardian host component to the user and spawns the guardian inside said component /// 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(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(target)) { _popupSystem.PopupEntity(Loc.GetString("guardian-already-present-invalid-creation"), user, Filter.Entities(user)); return; } // Can't work without actions EntityManager.EnsureComponent(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(ev.User, out var hands) || !hands.IsHolding(comp.Owner) || HasComp(ev.Target) || !TryComp(ev.Target, out var actions)) { comp.Injecting = false; return; } var hostXform = EntityManager.GetComponent(ev.Target); var host = EntityManager.EnsureComponent(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); } } /// /// Triggers when the host receives damage which puts the host in either critical or killed state /// 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(uid); } } /// /// Handles guardian receiving damage and splitting it with the host according to his defence percent /// 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)); } /// /// Triggers while trying to examine an activator to see if it's used /// private void OnCreatorExamine(EntityUid uid, GuardianCreatorComponent component, ExaminedEvent args) { if (component.Used) { args.PushMarkup(Loc.GetString("guardian-activator-empty-examine")); } } /// /// Called every time the host moves, to make sure the distance between the host and the guardian isn't too far /// 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); } /// /// Called every time the guardian moves: makes sure it's not out of it's allowed distance /// private void OnGuardianMove(EntityUid uid, GuardianComponent component, ref MoveEvent args) { if (!component.GuardianLoose) return; CheckGuardianMove(component.Host, uid, guardianComponent: component); } /// /// Retract the guardian if either the host or the guardian move away from each other. /// 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(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; } } } }