Files
tbd-station-14/Content.Server/Flash/FlashSystem.cs
Pieter-Jan Briers 68ce53ae17 Random spontaneous cleanup PR (#25131)
* Use new Subs.CVar helper

Removes manual config OnValueChanged calls, removes need to remember to manually unsubscribe.

This both reduces boilerplate and fixes many issues where subscriptions weren't removed on entity system shutdown.

* Fix a bunch of warnings

* More warning fixes

* Use new DateTime serializer to get rid of ISerializationHooks in changelog code.

* Get rid of some more ISerializationHooks for enums

* And a little more

* Apply suggestions from code review

Co-authored-by: 0x6273 <0x40@keemail.me>

---------

Co-authored-by: 0x6273 <0x40@keemail.me>
2024-02-13 16:48:39 -05:00

242 lines
9.0 KiB
C#

using System.Linq;
using Content.Server.Flash.Components;
using Content.Shared.Flash.Components;
using Content.Server.Light.EntitySystems;
using Content.Server.Popups;
using Content.Server.Stunnable;
using Content.Shared.Charges.Components;
using Content.Shared.Charges.Systems;
using Content.Shared.Eye.Blinding.Components;
using Content.Shared.Flash;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Interaction.Events;
using Content.Shared.Inventory;
using Content.Shared.Physics;
using Content.Shared.Tag;
using Content.Shared.Traits.Assorted;
using Content.Shared.Weapons.Melee.Events;
using Robust.Server.Audio;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
using Robust.Shared.Timing;
using InventoryComponent = Content.Shared.Inventory.InventoryComponent;
namespace Content.Server.Flash
{
internal sealed class FlashSystem : SharedFlashSystem
{
[Dependency] private readonly AppearanceSystem _appearance = default!;
[Dependency] private readonly AudioSystem _audio = default!;
[Dependency] private readonly SharedChargesSystem _charges = default!;
[Dependency] private readonly EntityLookupSystem _entityLookup = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
[Dependency] private readonly InventorySystem _inventory = default!;
[Dependency] private readonly PopupSystem _popup = default!;
[Dependency] private readonly StunSystem _stun = default!;
[Dependency] private readonly TagSystem _tag = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<FlashComponent, MeleeHitEvent>(OnFlashMeleeHit);
// ran before toggling light for extra-bright lantern
SubscribeLocalEvent<FlashComponent, UseInHandEvent>(OnFlashUseInHand, before: new []{ typeof(HandheldLightSystem) });
SubscribeLocalEvent<InventoryComponent, FlashAttemptEvent>(OnInventoryFlashAttempt);
SubscribeLocalEvent<FlashImmunityComponent, FlashAttemptEvent>(OnFlashImmunityFlashAttempt);
SubscribeLocalEvent<PermanentBlindnessComponent, FlashAttemptEvent>(OnPermanentBlindnessFlashAttempt);
SubscribeLocalEvent<TemporaryBlindnessComponent, FlashAttemptEvent>(OnTemporaryBlindnessFlashAttempt);
}
private void OnFlashMeleeHit(EntityUid uid, FlashComponent comp, MeleeHitEvent args)
{
if (!args.IsHit ||
!args.HitEntities.Any() ||
!UseFlash(uid, comp, args.User))
{
return;
}
args.Handled = true;
foreach (var e in args.HitEntities)
{
Flash(e, args.User, uid, comp.FlashDuration, comp.SlowTo, melee: true);
}
}
private void OnFlashUseInHand(EntityUid uid, FlashComponent comp, UseInHandEvent args)
{
if (args.Handled || !UseFlash(uid, comp, args.User))
return;
args.Handled = true;
FlashArea(uid, args.User, comp.Range, comp.AoeFlashDuration, comp.SlowTo, true);
}
private bool UseFlash(EntityUid uid, FlashComponent comp, EntityUid user)
{
if (comp.Flashing)
return false;
TryComp<LimitedChargesComponent>(uid, out var charges);
if (_charges.IsEmpty(uid, charges))
return false;
_charges.UseCharge(uid, charges);
_audio.PlayPvs(comp.Sound, uid);
comp.Flashing = true;
_appearance.SetData(uid, FlashVisuals.Flashing, true);
if (_charges.IsEmpty(uid, charges))
{
_appearance.SetData(uid, FlashVisuals.Burnt, true);
_tag.AddTag(uid, "Trash");
_popup.PopupEntity(Loc.GetString("flash-component-becomes-empty"), user);
}
uid.SpawnTimer(400, () =>
{
_appearance.SetData(uid, FlashVisuals.Flashing, false);
comp.Flashing = false;
});
return true;
}
public void Flash(EntityUid target,
EntityUid? user,
EntityUid? used,
float flashDuration,
float slowTo,
bool displayPopup = true,
FlashableComponent? flashable = null,
bool melee = false)
{
if (!Resolve(target, ref flashable, false))
return;
var attempt = new FlashAttemptEvent(target, user, used);
RaiseLocalEvent(target, attempt, true);
if (attempt.Cancelled)
return;
if (melee)
{
var ev = new AfterFlashedEvent(target, user, used);
if (user != null)
RaiseLocalEvent(user.Value, ref ev);
if (used != null)
RaiseLocalEvent(used.Value, ref ev);
}
flashable.LastFlash = _timing.CurTime;
flashable.Duration = flashDuration / 1000f; // TODO: Make this sane...
Dirty(target, flashable);
_stun.TrySlowdown(target, TimeSpan.FromSeconds(flashDuration/1000f), true,
slowTo, slowTo);
if (displayPopup && user != null && target != user && Exists(user.Value))
{
_popup.PopupEntity(Loc.GetString("flash-component-user-blinds-you",
("user", Identity.Entity(user.Value, EntityManager))), target, target);
}
}
public void FlashArea(EntityUid source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, SoundSpecifier? sound = null)
{
var transform = EntityManager.GetComponent<TransformComponent>(source);
var mapPosition = transform.MapPosition;
var flashableEntities = new List<EntityUid>();
var flashableQuery = GetEntityQuery<FlashableComponent>();
foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range))
{
if (!flashableQuery.HasComponent(entity))
continue;
flashableEntities.Add(entity);
}
foreach (var entity in flashableEntities)
{
// Check for unobstructed entities while ignoring the mobs with flashable components.
if (!_interaction.InRangeUnobstructed(entity, mapPosition, range, CollisionGroup.Opaque, (e) => flashableEntities.Contains(e) || e == source))
continue;
// They shouldn't have flash removed in between right?
Flash(entity, user, source, duration, slowTo, displayPopup, flashableQuery.GetComponent(entity));
}
if (sound != null)
{
_audio.PlayPvs(sound, source, AudioParams.Default.WithVolume(1f).WithMaxDistance(3f));
}
}
private void OnInventoryFlashAttempt(EntityUid uid, InventoryComponent component, FlashAttemptEvent args)
{
foreach (var slot in new[] { "head", "eyes", "mask" })
{
if (args.Cancelled)
break;
if (_inventory.TryGetSlotEntity(uid, slot, out var item, component))
RaiseLocalEvent(item.Value, args, true);
}
}
private void OnFlashImmunityFlashAttempt(EntityUid uid, FlashImmunityComponent component, FlashAttemptEvent args)
{
if(component.Enabled)
args.Cancel();
}
private void OnPermanentBlindnessFlashAttempt(EntityUid uid, PermanentBlindnessComponent component, FlashAttemptEvent args)
{
args.Cancel();
}
private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args)
{
args.Cancel();
}
}
public sealed class FlashAttemptEvent : CancellableEntityEventArgs
{
public readonly EntityUid Target;
public readonly EntityUid? User;
public readonly EntityUid? Used;
public FlashAttemptEvent(EntityUid target, EntityUid? user, EntityUid? used)
{
Target = target;
User = user;
Used = used;
}
}
/// <summary>
/// Called after a flash is used via melee on another person to check for rev conversion.
/// Raised on the user of the flash, the target hit by the flash, and the flash used.
/// </summary>
[ByRefEvent]
public readonly struct AfterFlashedEvent
{
public readonly EntityUid Target;
public readonly EntityUid? User;
public readonly EntityUid? Used;
public AfterFlashedEvent(EntityUid target, EntityUid? user, EntityUid? used)
{
Target = target;
User = user;
Used = used;
}
}
}