| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239 |
- using System.Diagnostics.CodeAnalysis;
- using System.Numerics;
- using Content.Shared.Administration.Logs;
- using Content.Shared.Alert;
- using Content.Shared.Audio;
- using Content.Shared.Database;
- using Content.Shared.Hands;
- using Content.Shared.Inventory;
- using Content.Shared.Inventory.Events;
- using Content.Shared.Item.ItemToggle;
- using Content.Shared.Item.ItemToggle.Components;
- using Content.Shared.Popups;
- using Content.Shared.Projectiles;
- using Content.Shared.Weapons.Ranged.Components;
- using Content.Shared.Weapons.Ranged.Events;
- using Robust.Shared.Audio;
- using Robust.Shared.Audio.Systems;
- using Robust.Shared.Network;
- using Robust.Shared.Physics.Components;
- using Robust.Shared.Physics.Systems;
- using Robust.Shared.Random;
- using Robust.Shared.Timing;
- namespace Content.Shared.Weapons.Reflect;
- /// <summary>
- /// This handles reflecting projectiles and hitscan shots.
- /// </summary>
- public sealed class ReflectSystem : EntitySystem
- {
- [Dependency] private readonly IGameTiming _gameTiming = default!;
- [Dependency] private readonly INetManager _netManager = default!;
- [Dependency] private readonly IRobustRandom _random = default!;
- [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
- [Dependency] private readonly ItemToggleSystem _toggle = default!;
- [Dependency] private readonly SharedPopupSystem _popup = default!;
- [Dependency] private readonly SharedPhysicsSystem _physics = default!;
- [Dependency] private readonly SharedAudioSystem _audio = default!;
- [Dependency] private readonly SharedTransformSystem _transform = default!;
- [Dependency] private readonly InventorySystem _inventorySystem = default!;
- public override void Initialize()
- {
- base.Initialize();
- SubscribeLocalEvent<ReflectComponent, ProjectileReflectAttemptEvent>(OnReflectCollide);
- SubscribeLocalEvent<ReflectComponent, HitScanReflectAttemptEvent>(OnReflectHitscan);
- SubscribeLocalEvent<ReflectComponent, GotEquippedEvent>(OnReflectEquipped);
- SubscribeLocalEvent<ReflectComponent, GotUnequippedEvent>(OnReflectUnequipped);
- SubscribeLocalEvent<ReflectComponent, GotEquippedHandEvent>(OnReflectHandEquipped);
- SubscribeLocalEvent<ReflectComponent, GotUnequippedHandEvent>(OnReflectHandUnequipped);
- SubscribeLocalEvent<ReflectComponent, ItemToggledEvent>(OnToggleReflect);
- SubscribeLocalEvent<ReflectUserComponent, ProjectileReflectAttemptEvent>(OnReflectUserCollide);
- SubscribeLocalEvent<ReflectUserComponent, HitScanReflectAttemptEvent>(OnReflectUserHitscan);
- }
- private void OnReflectUserHitscan(EntityUid uid, ReflectUserComponent component, ref HitScanReflectAttemptEvent args)
- {
- if (args.Reflected)
- return;
- foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
- {
- if (!TryReflectHitscan(uid, ent, args.Shooter, args.SourceItem, args.Direction, out var dir))
- continue;
- args.Direction = dir.Value;
- args.Reflected = true;
- break;
- }
- }
- private void OnReflectUserCollide(EntityUid uid, ReflectUserComponent component, ref ProjectileReflectAttemptEvent args)
- {
- foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(uid, SlotFlags.All & ~SlotFlags.POCKET))
- {
- if (!TryReflectProjectile(uid, ent, args.ProjUid))
- continue;
- args.Cancelled = true;
- break;
- }
- }
- private void OnReflectCollide(EntityUid uid, ReflectComponent component, ref ProjectileReflectAttemptEvent args)
- {
- if (args.Cancelled)
- return;
- if (TryReflectProjectile(uid, uid, args.ProjUid, reflect: component))
- args.Cancelled = true;
- }
- private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null)
- {
- if (!Resolve(reflector, ref reflect, false) ||
- !_toggle.IsActivated(reflector) ||
- !TryComp<ReflectiveComponent>(projectile, out var reflective) ||
- (reflect.Reflects & reflective.Reflective) == 0x0 ||
- !_random.Prob(reflect.ReflectProb) ||
- !TryComp<PhysicsComponent>(projectile, out var physics))
- {
- return false;
- }
- var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite();
- var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics);
- var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(user);
- var newVelocity = rotation.RotateVec(relativeVelocity);
- // Have the velocity in world terms above so need to convert it back to local.
- var difference = newVelocity - existingVelocity;
- _physics.SetLinearVelocity(projectile, physics.LinearVelocity + difference, body: physics);
- var locRot = Transform(projectile).LocalRotation;
- var newRot = rotation.RotateVec(locRot.ToVec());
- _transform.SetLocalRotation(projectile, newRot.ToAngle());
- if (_netManager.IsServer)
- {
- _popup.PopupEntity(Loc.GetString("reflect-shot"), user);
- _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
- }
- if (Resolve(projectile, ref projectileComp, false))
- {
- _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}");
- projectileComp.Shooter = user;
- projectileComp.Weapon = user;
- Dirty(projectile, projectileComp);
- }
- else
- {
- _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)}");
- }
- return true;
- }
- private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args)
- {
- if (args.Reflected ||
- (component.Reflects & args.Reflective) == 0x0)
- {
- return;
- }
- if (TryReflectHitscan(uid, uid, args.Shooter, args.SourceItem, args.Direction, out var dir))
- {
- args.Direction = dir.Value;
- args.Reflected = true;
- }
- }
- private bool TryReflectHitscan(
- EntityUid user,
- EntityUid reflector,
- EntityUid? shooter,
- EntityUid shotSource,
- Vector2 direction,
- [NotNullWhen(true)] out Vector2? newDirection)
- {
- if (!TryComp<ReflectComponent>(reflector, out var reflect) ||
- !_toggle.IsActivated(reflector) ||
- !_random.Prob(reflect.ReflectProb))
- {
- newDirection = null;
- return false;
- }
- if (_netManager.IsServer)
- {
- _popup.PopupEntity(Loc.GetString("reflect-shot"), user);
- _audio.PlayPvs(reflect.SoundOnReflect, user, AudioHelpers.WithVariation(0.05f, _random));
- }
- var spread = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2);
- newDirection = -spread.RotateVec(direction);
- if (shooter != null)
- _adminLogger.Add(LogType.HitScanHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected hitscan from {ToPrettyString(shotSource)} shot by {ToPrettyString(shooter.Value)}");
- else
- _adminLogger.Add(LogType.HitScanHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected hitscan from {ToPrettyString(shotSource)}");
- return true;
- }
- private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEquippedEvent args)
- {
- if (_gameTiming.ApplyingState)
- return;
- EnsureComp<ReflectUserComponent>(args.Equipee);
- }
- private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args)
- {
- RefreshReflectUser(args.Equipee);
- }
- private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, GotEquippedHandEvent args)
- {
- if (_gameTiming.ApplyingState)
- return;
- EnsureComp<ReflectUserComponent>(args.User);
- }
- private void OnReflectHandUnequipped(EntityUid uid, ReflectComponent component, GotUnequippedHandEvent args)
- {
- RefreshReflectUser(args.User);
- }
- private void OnToggleReflect(EntityUid uid, ReflectComponent comp, ref ItemToggledEvent args)
- {
- if (args.User is {} user)
- RefreshReflectUser(user);
- }
- /// <summary>
- /// Refreshes whether someone has reflection potential so we can raise directed events on them.
- /// </summary>
- private void RefreshReflectUser(EntityUid user)
- {
- foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET))
- {
- if (!HasComp<ReflectComponent>(ent) || !_toggle.IsActivated(ent))
- continue;
- EnsureComp<ReflectUserComponent>(user);
- return;
- }
- RemCompDeferred<ReflectUserComponent>(user);
- }
- }
|