diff --git a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs index 742e632501..aa367f71f7 100644 --- a/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs +++ b/Content.Shared/Hands/EntitySystems/SharedHandsSystem.Relay.cs @@ -2,6 +2,8 @@ using Content.Shared.Atmos; using Content.Shared.Camera; using Content.Shared.Hands.Components; using Content.Shared.Movement.Systems; +using Content.Shared.Projectiles; +using Content.Shared.Weapons.Ranged.Events; namespace Content.Shared.Hands.EntitySystems; @@ -15,6 +17,8 @@ public abstract partial class SharedHandsSystem // By-ref events. SubscribeLocalEvent(RefRelayEvent); + SubscribeLocalEvent(RefRelayEvent); + SubscribeLocalEvent(RefRelayEvent); } private void RelayEvent(Entity entity, ref T args) where T : EntityEventArgs diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index ce245992a9..ed456ed47b 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -17,6 +17,7 @@ using Content.Shared.Movement.Events; using Content.Shared.Movement.Systems; using Content.Shared.NameModifier.EntitySystems; using Content.Shared.Overlays; +using Content.Shared.Projectiles; using Content.Shared.Radio; using Content.Shared.Slippery; using Content.Shared.Strip.Components; @@ -56,6 +57,8 @@ public partial class InventorySystem SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); + SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); // Eye/vision events diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 3fcff5ae56..20d1e051a2 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -4,6 +4,7 @@ using Content.Shared.Damage; using Content.Shared.DoAfter; using Content.Shared.Hands.EntitySystems; using Content.Shared.Interaction; +using Content.Shared.Inventory; using Content.Shared.Mobs.Components; using Content.Shared.Throwing; using Robust.Shared.Audio.Systems; @@ -238,7 +239,10 @@ public sealed class ImpactEffectEvent : EntityEventArgs /// Raised when an entity is just about to be hit with a projectile but can reflect it /// [ByRefEvent] -public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled); +public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled) : IInventoryRelayEvent +{ + SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET; +} /// /// Raised when a projectile hits an entity diff --git a/Content.Shared/Weapons/Ranged/Events/HitScanReflectAttempt.cs b/Content.Shared/Weapons/Ranged/Events/HitScanReflectAttempt.cs index 31c9c4cdd4..f1a675d5c6 100644 --- a/Content.Shared/Weapons/Ranged/Events/HitScanReflectAttempt.cs +++ b/Content.Shared/Weapons/Ranged/Events/HitScanReflectAttempt.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Shared.Inventory; using Content.Shared.Weapons.Reflect; namespace Content.Shared.Weapons.Ranged.Events; @@ -8,4 +9,7 @@ namespace Content.Shared.Weapons.Ranged.Events; /// and changing where shot will go next /// [ByRefEvent] -public record struct HitScanReflectAttemptEvent(EntityUid? Shooter, EntityUid SourceItem, ReflectType Reflective, Vector2 Direction, bool Reflected); +public record struct HitScanReflectAttemptEvent(EntityUid? Shooter, EntityUid SourceItem, ReflectType Reflective, Vector2 Direction, bool Reflected) : IInventoryRelayEvent +{ + SlotFlags IInventoryRelayEvent.TargetSlots => SlotFlags.WITHOUT_POCKET; +} diff --git a/Content.Shared/Weapons/Reflect/ReflectComponent.cs b/Content.Shared/Weapons/Reflect/ReflectComponent.cs index 703d8904dc..ea906810a5 100644 --- a/Content.Shared/Weapons/Reflect/ReflectComponent.cs +++ b/Content.Shared/Weapons/Reflect/ReflectComponent.cs @@ -1,5 +1,7 @@ +using Content.Shared.Inventory; using Robust.Shared.Audio; using Robust.Shared.GameStates; +using Robust.Shared.Serialization; namespace Content.Shared.Weapons.Reflect; @@ -13,23 +15,48 @@ public sealed partial class ReflectComponent : Component /// /// What we reflect. /// - [ViewVariables(VVAccess.ReadWrite), DataField] + [DataField] public ReflectType Reflects = ReflectType.Energy | ReflectType.NonEnergy; + /// + /// Select in which inventory slots it will reflect. + /// By default, it will reflect in any inventory position, except pockets. + /// + [DataField] + public SlotFlags SlotFlags = SlotFlags.WITHOUT_POCKET; + + /// + /// Is it allowed to reflect while being in hands. + /// + [DataField, AutoNetworkedField] + public bool ReflectingInHands = true; + + /// + /// Can only reflect when placed correctly. + /// + [DataField, AutoNetworkedField] + public bool InRightPlace; + /// /// Probability for a projectile to be reflected. /// - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + [DataField, AutoNetworkedField] public float ReflectProb = 0.25f; - [DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] + /// + /// Probability for a projectile to be reflected. + /// + [DataField, AutoNetworkedField] public Angle Spread = Angle.FromDegrees(45); + /// + /// The sound to play when reflecting. + /// [DataField] public SoundSpecifier? SoundOnReflect = new SoundPathSpecifier("/Audio/Weapons/Guns/Hits/laser_sear_wall.ogg", AudioParams.Default.WithVariation(0.05f)); } -[Flags] +[Flags, Serializable, NetSerializable] public enum ReflectType : byte { None = 0, diff --git a/Content.Shared/Weapons/Reflect/ReflectSystem.cs b/Content.Shared/Weapons/Reflect/ReflectSystem.cs index 3d3ded99ce..1ca2cef4e4 100644 --- a/Content.Shared/Weapons/Reflect/ReflectSystem.cs +++ b/Content.Shared/Weapons/Reflect/ReflectSystem.cs @@ -1,25 +1,20 @@ 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; @@ -28,7 +23,6 @@ namespace Content.Shared.Weapons.Reflect; /// 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!; @@ -37,74 +31,82 @@ public sealed class ReflectSystem : EntitySystem [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(); + Subs.SubscribeWithRelay(OnReflectUserCollide, baseEvent: false); + Subs.SubscribeWithRelay(OnReflectUserHitscan, baseEvent: false); SubscribeLocalEvent(OnReflectCollide); SubscribeLocalEvent(OnReflectHitscan); + SubscribeLocalEvent(OnReflectEquipped); SubscribeLocalEvent(OnReflectUnequipped); SubscribeLocalEvent(OnReflectHandEquipped); SubscribeLocalEvent(OnReflectHandUnequipped); - SubscribeLocalEvent(OnToggleReflect); - - SubscribeLocalEvent(OnReflectUserCollide); - SubscribeLocalEvent(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, args.Reflective, 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) + private void OnReflectUserCollide(Entity ent, ref ProjectileReflectAttemptEvent args) { if (args.Cancelled) return; - if (TryReflectProjectile(uid, uid, args.ProjUid, reflect: component)) + if (!ent.Comp.InRightPlace) + return; // only reflect when equipped correctly + + if (TryReflectProjectile(ent, ent.Owner, args.ProjUid)) args.Cancelled = true; } - private bool TryReflectProjectile(EntityUid user, EntityUid reflector, EntityUid projectile, ProjectileComponent? projectileComp = null, ReflectComponent? reflect = null) + private void OnReflectUserHitscan(Entity ent, ref HitScanReflectAttemptEvent args) { - if (!Resolve(reflector, ref reflect, false) || - !TryComp(projectile, out var reflective) || - (reflect.Reflects & reflective.Reflective) == 0x0 || - !_toggle.IsActivated(reflector) || - !_random.Prob(reflect.ReflectProb) || + if (args.Reflected) + return; + + if (!ent.Comp.InRightPlace) + return; // only reflect when equipped correctly + + if (TryReflectHitscan(ent, ent.Owner, args.Shooter, args.SourceItem, args.Direction, args.Reflective, out var dir)) + { + args.Direction = dir.Value; + args.Reflected = true; + } + } + + private void OnReflectCollide(Entity ent, ref ProjectileReflectAttemptEvent args) + { + if (args.Cancelled) + return; + + if (TryReflectProjectile(ent, ent.Owner, args.ProjUid)) + args.Cancelled = true; + } + + private void OnReflectHitscan(Entity ent, ref HitScanReflectAttemptEvent args) + { + if (args.Reflected) + return; + + if (TryReflectHitscan(ent, ent.Owner, args.Shooter, args.SourceItem, args.Direction, args.Reflective, out var dir)) + { + args.Direction = dir.Value; + args.Reflected = true; + } + } + + private bool TryReflectProjectile(Entity reflector, EntityUid user, Entity projectile) + { + if (!TryComp(projectile, out var reflective) || + (reflector.Comp.Reflects & reflective.Reflective) == 0x0 || + !_toggle.IsActivated(reflector.Owner) || + !_random.Prob(reflector.Comp.ReflectProb) || !TryComp(projectile, out var physics)) { return false; } - var rotation = _random.NextAngle(-reflect.Spread / 2, reflect.Spread / 2).Opposite(); + var rotation = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.Spread / 2).Opposite(); var existingVelocity = _physics.GetMapLinearVelocity(projectile, component: physics); var relativeVelocity = existingVelocity - _physics.GetMapLinearVelocity(user); var newVelocity = rotation.RotateVec(relativeVelocity); @@ -118,15 +120,15 @@ public sealed class ReflectSystem : EntitySystem var newRot = rotation.RotateVec(locRot.ToVec()); _transform.SetLocalRotation(projectile, newRot.ToAngle()); - PlayAudioAndPopup(reflect, user); + PlayAudioAndPopup(reflector.Comp, user); - if (Resolve(projectile, ref projectileComp, false)) + if (Resolve(projectile, ref projectile.Comp, false)) { - _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectileComp.Weapon)} shot by {projectileComp.Shooter}"); + _adminLogger.Add(LogType.BulletHit, LogImpact.Medium, $"{ToPrettyString(user)} reflected {ToPrettyString(projectile)} from {ToPrettyString(projectile.Comp.Weapon)} shot by {projectile.Comp.Shooter}"); - projectileComp.Shooter = user; - projectileComp.Weapon = user; - Dirty(projectile, projectileComp); + projectile.Comp.Shooter = user; + projectile.Comp.Weapon = user; + Dirty(projectile, projectile.Comp); } else { @@ -135,19 +137,34 @@ public sealed class ReflectSystem : EntitySystem return true; } - - private void OnReflectHitscan(EntityUid uid, ReflectComponent component, ref HitScanReflectAttemptEvent args) + private bool TryReflectHitscan( + Entity reflector, + EntityUid user, + EntityUid? shooter, + EntityUid shotSource, + Vector2 direction, + ReflectType hitscanReflectType, + [NotNullWhen(true)] out Vector2? newDirection) { - if (args.Reflected) + if ((reflector.Comp.Reflects & hitscanReflectType) == 0x0 || + !_toggle.IsActivated(reflector.Owner) || + !_random.Prob(reflector.Comp.ReflectProb)) { - return; + newDirection = null; + return false; } - if (TryReflectHitscan(uid, uid, args.Shooter, args.SourceItem, args.Direction, args.Reflective, out var dir)) - { - args.Direction = dir.Value; - args.Reflected = true; - } + PlayAudioAndPopup(reflector.Comp, user); + + var spread = _random.NextAngle(-reflector.Comp.Spread / 2, reflector.Comp.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 PlayAudioAndPopup(ReflectComponent reflect, EntityUid user) @@ -160,83 +177,27 @@ public sealed class ReflectSystem : EntitySystem } } - private bool TryReflectHitscan( - EntityUid user, - EntityUid reflector, - EntityUid? shooter, - EntityUid shotSource, - Vector2 direction, - ReflectType hitscanReflectType, - [NotNullWhen(true)] out Vector2? newDirection) + private void OnReflectEquipped(Entity ent, ref GotEquippedEvent args) { - if (!TryComp(reflector, out var reflect) || - (reflect.Reflects & hitscanReflectType) == 0x0 || - !_toggle.IsActivated(reflector) || - !_random.Prob(reflect.ReflectProb)) - { - newDirection = null; - return false; - } - - PlayAudioAndPopup(reflect, user); - - 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; + ent.Comp.InRightPlace = (ent.Comp.SlotFlags & args.SlotFlags) == args.SlotFlags; + Dirty(ent); } - private void OnReflectEquipped(EntityUid uid, ReflectComponent component, GotEquippedEvent args) + private void OnReflectUnequipped(Entity ent, ref GotUnequippedEvent args) { - if (_gameTiming.ApplyingState) - return; - - EnsureComp(args.Equipee); + ent.Comp.InRightPlace = false; + Dirty(ent); } - private void OnReflectUnequipped(EntityUid uid, ReflectComponent comp, GotUnequippedEvent args) + private void OnReflectHandEquipped(Entity ent, ref GotEquippedHandEvent args) { - RefreshReflectUser(args.Equipee); + ent.Comp.InRightPlace = ent.Comp.ReflectingInHands; + Dirty(ent); } - private void OnReflectHandEquipped(EntityUid uid, ReflectComponent component, GotEquippedHandEvent args) + private void OnReflectHandUnequipped(Entity ent, ref GotUnequippedHandEvent args) { - if (_gameTiming.ApplyingState) - return; - - EnsureComp(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); - } - - /// - /// Refreshes whether someone has reflection potential so we can raise directed events on them. - /// - private void RefreshReflectUser(EntityUid user) - { - foreach (var ent in _inventorySystem.GetHandOrInventoryEntities(user, SlotFlags.All & ~SlotFlags.POCKET)) - { - if (!HasComp(ent) || !_toggle.IsActivated(ent)) - continue; - - EnsureComp(user); - return; - } - - RemCompDeferred(user); + ent.Comp.InRightPlace = false; + Dirty(ent); } } diff --git a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs b/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs deleted file mode 100644 index 44ef481a37..0000000000 --- a/Content.Shared/Weapons/Reflect/ReflectUserComponent.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Reflect; - -/// -/// Added to an entity if it equips a reflection item in a hand slot or into its clothing. -/// Reflection events will then be relayed. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class ReflectUserComponent : Component; diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index 243759c871..07bf9ad351 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -83,6 +83,7 @@ reflectProb: 1 reflects: - Energy + reflectingInHands: false #Detective's vest - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml index f4d4c5a918..086052f77a 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/sword.yml @@ -169,6 +169,8 @@ - Back - Belt - type: Reflect + slotFlags: + - NONE #Greatswords - type: entity