| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- 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.Events;
- using Content.Shared.Inventory;
- using Content.Shared.Tag;
- using Content.Shared.Traits.Assorted;
- using Content.Shared.Weapons.Melee.Events;
- using Content.Shared.StatusEffect;
- using Content.Shared.Examine;
- using Robust.Server.Audio;
- using Robust.Server.GameObjects;
- using Robust.Shared.Audio;
- using Robust.Shared.Random;
- 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 SharedTransformSystem _transform = default!;
- [Dependency] private readonly ExamineSystemShared _examine = 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!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = 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, stunDuration: comp.MeleeStunDuration);
- }
- }
- 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, comp.Probability);
- }
- 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,
- bool melee = false,
- TimeSpan? stunDuration = null)
- {
- var attempt = new FlashAttemptEvent(target, user, used);
- RaiseLocalEvent(target, attempt, true);
- if (attempt.Cancelled)
- return;
- // don't paralyze, slowdown or convert to rev if the target is immune to flashes
- if (!_statusEffectsSystem.TryAddStatusEffect<FlashedComponent>(target, FlashedKey, TimeSpan.FromSeconds(flashDuration / 1000f), true))
- return;
- if (stunDuration != null)
- {
- _stun.TryParalyze(target, stunDuration.Value, true);
- }
- else
- {
- _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);
- }
- 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);
- }
- }
- public override void FlashArea(Entity<FlashComponent?> source, EntityUid? user, float range, float duration, float slowTo = 0.8f, bool displayPopup = false, float probability = 1f, SoundSpecifier? sound = null)
- {
- var transform = Transform(source);
- var mapPosition = _transform.GetMapCoordinates(transform);
- var statusEffectsQuery = GetEntityQuery<StatusEffectsComponent>();
- var damagedByFlashingQuery = GetEntityQuery<DamagedByFlashingComponent>();
- foreach (var entity in _entityLookup.GetEntitiesInRange(transform.Coordinates, range))
- {
- if (!_random.Prob(probability))
- continue;
- // Is the entity affected by the flash either through status effects or by taking damage?
- if (!statusEffectsQuery.HasComponent(entity) && !damagedByFlashingQuery.HasComponent(entity))
- continue;
- // Check for entites in view
- // put damagedByFlashingComponent in the predicate because shadow anomalies block vision.
- if (!_examine.InRangeUnOccluded(entity, mapPosition, range, predicate: (e) => damagedByFlashingQuery.HasComponent(e)))
- continue;
- // They shouldn't have flash removed in between right?
- Flash(entity, user, source, duration, slowTo, displayPopup);
- }
- _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)
- {
- // check for total blindness
- if (component.Blindness == 0)
- args.Cancel();
- }
- private void OnTemporaryBlindnessFlashAttempt(EntityUid uid, TemporaryBlindnessComponent component, FlashAttemptEvent args)
- {
- args.Cancel();
- }
- }
- /// <summary>
- /// Called before a flash is used to check if the attempt is cancelled by blindness, items or FlashImmunityComponent.
- /// Raised on the target hit by the flash, the user of the flash and the flash used.
- /// </summary>
- 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 target hit by the flash, the user of 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;
- }
- }
- }
|