diff --git a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs index 511d319655..15af9dc981 100644 --- a/Content.Client/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Client/Weapons/Ranged/Systems/GunSystem.cs @@ -9,8 +9,6 @@ using Content.Shared._Starlight.Weapon.Components; using Content.Shared._Sunrise.SunriseCCVars; using Content.Shared.Camera; using Content.Shared.CombatMode; -using Content.Shared.Damage; -using Content.Shared.Weapons.Hitscan.Components; using Content.Shared.Mech.Components; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; @@ -25,7 +23,6 @@ using Robust.Client.Input; using Robust.Client.Player; using Robust.Client.State; using Robust.Shared.Animations; -using Robust.Shared.Audio; using Robust.Shared.Input; using Robust.Shared.Map; using Robust.Shared.Map.Components; @@ -456,7 +453,6 @@ public sealed partial class GunSystem : SharedGunSystem continue; } - // TODO: Clean this up in a gun refactor at some point - too much copy pasting switch (shootable) { //🌟Starlight🌟 @@ -509,7 +505,7 @@ public sealed partial class GunSystem : SharedGunSystem else RemoveShootable(ent.Value); break; - case HitscanAmmoComponent: + case HitscanPrototype: Audio.PlayPredicted(gun.SoundGunshotModified, gunUid, user); Recoil(user, direction, gun.CameraRecoilScalarModified); break; @@ -647,7 +643,4 @@ public sealed partial class GunSystem : SharedGunSystem _animPlayer.Stop(gunUid, uidPlayer, "muzzle-flash-light"); _animPlayer.Play((gunUid, uidPlayer), animTwo, "muzzle-flash-light"); } - - // TODO: Move RangedDamageSoundComponent to shared so this can be predicted. - public override void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound) {} } diff --git a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs index 5d605e03c0..29363e6252 100644 --- a/Content.Server/Weapons/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapons/Ranged/Systems/GunSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Numerics; using Content.Server.Atmos.EntitySystems; using Content.Server.Atmos.Components; @@ -22,10 +23,11 @@ using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Systems; -using Content.Shared.Weapons.Hitscan.Components; -using Content.Shared.Weapons.Hitscan.Events; +using Content.Shared.Weapons.Reflect; +using Content.Shared.Damage.Components; using Robust.Shared.Audio; using Robust.Shared.Map; +using Robust.Shared.Physics; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Utility; @@ -153,7 +155,6 @@ public sealed partial class GunSystem : SharedGunSystem continue; } - // TODO: Clean this up in a gun refactor at some point - too much copy pasting switch (shootable) { //🌟Starlight🌟 @@ -242,7 +243,7 @@ public sealed partial class GunSystem : SharedGunSystem CreateAndFireProjectiles(ent.Value, newAmmo); break; - case HitscanAmmoComponent hitscan: + case HitscanPrototype hitscan: EntityUid? lastHit = null; List<(EntityCoordinates fromCoordinates, float distance, Angle mapDirection, EntityUid? hitEntity)> effects = []; @@ -296,7 +297,14 @@ public sealed partial class GunSystem : SharedGunSystem if (!ev.Reflected) break; - var hitscanEv = new HitscanTraceEvent + fromEffect = Transform(hit).Coordinates; + from = TransformSystem.ToMapCoordinates(fromEffect); + dir = ev.Direction; + lastUser = hit; + } + } + + if (lastHit != null) { var hitEntity = lastHit.Value; if (hitscan.StaminaDamage > 0f) @@ -678,7 +686,7 @@ public sealed partial class GunSystem : SharedGunSystem RaiseNetworkEvent(message, filter); } - public override void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound) + public void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound) { DebugTools.Assert(!Deleted(otherEntity), "Impact sound entity was deleted"); diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanAmmoComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanAmmoComponent.cs deleted file mode 100644 index 3993d5ad20..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanAmmoComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Content.Shared.Weapons.Ranged; -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// This component is used to indicate an entity is shootable from a hitscan weapon. -/// This is placed on the laser entity being shot, not the gun itself. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanAmmoComponent : Component, IShootable; diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicDamageComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanBasicDamageComponent.cs deleted file mode 100644 index dcbb19265e..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicDamageComponent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Content.Shared.Damage; -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// Hitscan entities that have this component will do the damage specified to hit targets (Who didn't reflect it). -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanBasicDamageComponent : Component -{ - /// - /// How much damage the hitscan weapon will do when hitting a target. - /// - [DataField(required: true)] - public DamageSpecifier Damage; -} diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicEffectsComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanBasicEffectsComponent.cs deleted file mode 100644 index 05209f3b9e..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicEffectsComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Robust.Shared.Audio; -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// System or basic "effects" like sounds and hit markers for hitscans. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanBasicEffectsComponent : Component -{ - /// - /// This will turn hit entities this color briefly. - /// - [DataField] - public Color? HitColor = Color.Red; - - /// - /// Sound that plays upon the thing being hit. - /// - [DataField] - public SoundSpecifier? Sound; - - /// - /// Force the hitscan sound to play rather than playing the entity's override sound (if it exists). - /// - [DataField] - public bool ForceSound; -} diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicRaycastComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanBasicRaycastComponent.cs deleted file mode 100644 index e04ab0da4f..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicRaycastComponent.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Content.Shared.Physics; -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// A basic raycast system that will shoot in a straight line when triggered. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanBasicRaycastComponent : Component -{ - /// - /// Maximum distance the raycast will travel before giving up. Reflections will reset the distance traveled - /// - [DataField] - public float MaxDistance = 20.0f; - - /// - /// The collision mask the hitscan ray uses to collide with other objects. See the enum for more information - /// - [DataField] - public CollisionGroup CollisionMask = CollisionGroup.Opaque; -} diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicVisualsComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanBasicVisualsComponent.cs deleted file mode 100644 index 519425ece3..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanBasicVisualsComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Utility; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// Provides basic visuals for hitscan weapons - works with -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanBasicVisualsComponent : Component -{ - /// - /// The muzzle flash from the hitscan weapon. - /// - [DataField] - public SpriteSpecifier? MuzzleFlash; - - /// - /// The "travel" sprite, this gets repeated until it hits the target. - /// - [DataField] - public SpriteSpecifier? TravelFlash; - - /// - /// The sprite that gets shown on the impact of the laser. - /// - [DataField] - public SpriteSpecifier? ImpactFlash; -} diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanReflectComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanReflectComponent.cs deleted file mode 100644 index a63f3ab670..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanReflectComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Content.Shared.Weapons.Reflect; -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// Hitscan entities with this component will get reflected by certain things (E.G energy swords). -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanReflectComponent : Component -{ - /// - /// The reflective type, will only reflect from entities that have a matching reflection type. - /// - [DataField] - public ReflectType ReflectiveType = ReflectType.Energy; - - /// - /// The maximum number of reflections the laser will make. - /// - [DataField] - public int MaxReflections = 3; - - /// - /// Current number of times this hitscan entity was reflected. Will not be more than - /// - [DataField] - public int CurrentReflections; -} diff --git a/Content.Shared/Weapons/Hitscan/Components/HitscanStaminaDamageComponent.cs b/Content.Shared/Weapons/Hitscan/Components/HitscanStaminaDamageComponent.cs deleted file mode 100644 index 35eb398682..0000000000 --- a/Content.Shared/Weapons/Hitscan/Components/HitscanStaminaDamageComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Robust.Shared.GameStates; - -namespace Content.Shared.Weapons.Hitscan.Components; - -/// -/// Hitscan entities that have this component will deal stamina damage to the target. -/// -[RegisterComponent, NetworkedComponent] -public sealed partial class HitscanStaminaDamageComponent : Component -{ - /// - /// How much stamania damage the hitscan weapon will do when hitting a target. - /// - [DataField] - public float StaminaDamage = 10.0f; -} diff --git a/Content.Shared/Weapons/Hitscan/Events/HitscanEvents.cs b/Content.Shared/Weapons/Hitscan/Events/HitscanEvents.cs deleted file mode 100644 index 0ebc18079a..0000000000 --- a/Content.Shared/Weapons/Hitscan/Events/HitscanEvents.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System.Numerics; -using Content.Shared.Damage; -using Robust.Shared.Map; - -namespace Content.Shared.Weapons.Hitscan.Events; - -/// -/// Raised on the hitscan entity when "fired". This could be from reflections or from the gun. This is the catalyst that -/// other systems will listen for to actually shoot the gun. -/// -[ByRefEvent] -public record struct HitscanTraceEvent -{ - /// - /// Location the hitscan was fired from. - /// - public EntityCoordinates FromCoordinates; - - /// - /// Direction that the ray was fired towards. - /// - public Vector2 ShotDirection; - - /// - /// Gun that was fired - this will always be the original weapon even if reflected. - /// - public EntityUid Gun; - - /// - /// Player who shot the gun, if null the gun was fired by itself. - /// - public EntityUid? Shooter; - - /// - /// Target that was being aimed at (Not necessarily hit). - /// - public EntityUid? Target; -} - -/// -/// All data known data for when a hitscan is actually fired. -/// -public record struct HitscanRaycastFiredData -{ - /// - /// Direction that the ray was fired towards. - /// - public Vector2 ShotDirection; - - /// - /// The entity that got hit, if null the raycast didn't hit anyone. - /// - public EntityUid? HitEntity; - - /// - /// Gun that fired the raycast. - /// - public EntityUid Gun; - - /// - /// Player who shot the gun, if null the gun was fired by itself. - /// - public EntityUid? Shooter; -} - -/// -/// Try to hit the targeted entity with a hitscan laser. Stuff like the reflection system should listen for this and -/// cancel the event if the laser was reflected. -/// -[ByRefEvent] -public struct AttemptHitscanRaycastFiredEvent -{ - /// - /// Data for the hitscan that was fired. - /// - public HitscanRaycastFiredData Data; - - /// - /// Set to true the hitscan is cancelled (e.g. due to reflection). - /// Cancelled hitscans should not apply damage or trigger follow-up effects. - /// - public bool Cancelled; -} - -/// -/// Results of a hitscan raycast and will be raised on the raycast entity on itself. Stuff like the damage system should -/// listen for this. At this point we KNOW the laser hit the entity. -/// -[ByRefEvent] -public struct HitscanRaycastFiredEvent -{ - /// - /// Data for the hitscan that was fired. - /// - public HitscanRaycastFiredData Data; -} - -[ByRefEvent] -public record struct HitscanDamageDealtEvent -{ - /// - /// Target that was dealt damage. - /// - public EntityUid Target; - - /// - /// The amount of damage that the target was dealt. - /// - public DamageSpecifier DamageDealt; -} diff --git a/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicDamageSystem.cs b/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicDamageSystem.cs deleted file mode 100644 index 130687469a..0000000000 --- a/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicDamageSystem.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Content.Shared.Damage; -using Content.Shared.Weapons.Hitscan.Components; -using Content.Shared.Weapons.Hitscan.Events; - -namespace Content.Shared.Weapons.Hitscan.Systems; - -public sealed class HitscanBasicDamageSystem : EntitySystem -{ - [Dependency] private readonly DamageableSystem _damage = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnHitscanHit); - } - - private void OnHitscanHit(Entity ent, ref HitscanRaycastFiredEvent args) - { - if (args.Data.HitEntity == null) - return; - - var dmg = ent.Comp.Damage * _damage.UniversalHitscanDamageModifier; - - var damageDealt = _damage.TryChangeDamage(args.Data.HitEntity, dmg, origin: args.Data.Gun); - - if (damageDealt == null) - return; - - var damageEvent = new HitscanDamageDealtEvent - { - Target = args.Data.HitEntity.Value, - DamageDealt = damageDealt, - }; - - RaiseLocalEvent(ent, ref damageEvent); - } -} diff --git a/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicEffectsSystem.cs b/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicEffectsSystem.cs deleted file mode 100644 index 4713734be7..0000000000 --- a/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicEffectsSystem.cs +++ /dev/null @@ -1,36 +0,0 @@ -using Content.Shared.Damage; -using Content.Shared.Effects; -using Content.Shared.Weapons.Hitscan.Components; -using Content.Shared.Weapons.Hitscan.Events; -using Content.Shared.Weapons.Ranged.Systems; -using Robust.Shared.Player; - -namespace Content.Shared.Weapons.Hitscan.Systems; - -public sealed class HitscanBasicEffectsSystem : EntitySystem -{ - [Dependency] private readonly SharedColorFlashEffectSystem _color = default!; - [Dependency] private readonly SharedGunSystem _gun = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnHitscanDamageDealt); - } - - private void OnHitscanDamageDealt(Entity ent, ref HitscanDamageDealtEvent args) - { - if (Deleted(args.Target)) - return; - - if (ent.Comp.HitColor != null && args.DamageDealt.GetTotal() != 0) - { - _color.RaiseEffect(ent.Comp.HitColor.Value, - new List { args.Target }, - Filter.Pvs(args.Target, entityManager: EntityManager)); - } - - _gun.PlayImpactSound(args.Target, args.DamageDealt, ent.Comp.Sound, ent.Comp.ForceSound); - } -} diff --git a/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicRaycastSystem.cs b/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicRaycastSystem.cs deleted file mode 100644 index 4bcfe8a69f..0000000000 --- a/Content.Shared/Weapons/Hitscan/Systems/HitscanBasicRaycastSystem.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System.Numerics; -using Content.Shared.Administration.Logs; -using Content.Shared.Damage.Components; -using Content.Shared.Database; -using Content.Shared.Weapons.Hitscan.Components; -using Content.Shared.Weapons.Hitscan.Events; -using Content.Shared.Weapons.Ranged.Systems; -using Robust.Shared.Containers; -using Robust.Shared.Map; -using Robust.Shared.Physics; -using Robust.Shared.Physics.Systems; -using Robust.Shared.Player; -using Robust.Shared.Utility; - -namespace Content.Shared.Weapons.Hitscan.Systems; - -public sealed class HitscanBasicRaycastSystem : EntitySystem -{ - [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly SharedContainerSystem _container = default!; - [Dependency] private readonly ISharedAdminLogManager _log = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - - private EntityQuery _visualsQuery; - - public override void Initialize() - { - base.Initialize(); - - _visualsQuery = GetEntityQuery(); - - SubscribeLocalEvent(OnHitscanFired); - } - - private void OnHitscanFired(Entity ent, ref HitscanTraceEvent args) - { - var shooter = args.Shooter ?? args.Gun; - var mapCords = _transform.ToMapCoordinates(args.FromCoordinates); - var ray = new CollisionRay(mapCords.Position, args.ShotDirection, (int) ent.Comp.CollisionMask); - var rayCastResults = _physics.IntersectRay(mapCords.MapId, ray, ent.Comp.MaxDistance, shooter, false); - - var target = args.Target; - // If you are in a container, use the raycast result - // Otherwise: - // 1.) Hit the first entity that you targeted. - // 2.) Hit the first entity that doesn't require you to aim at it specifically to be hit. - var result = _container.IsEntityOrParentInContainer(shooter) - ? rayCastResults.FirstOrNull() - : rayCastResults.FirstOrNull(hit => hit.HitEntity == target - || CompOrNull(hit.HitEntity)?.Active != true); - - var distanceTried = result?.Distance ?? ent.Comp.MaxDistance; - - // Do visuals without an event. They should always happen and putting it on the attempt event is weird! - // If more stuff gets added here, it should probably be turned into an event. - FireEffects(args.FromCoordinates, distanceTried, args.ShotDirection.ToAngle(), ent.Owner); - - // Admin logging - if (result?.HitEntity != null) - { - _log.Add(LogType.HitScanHit, - $"{ToPrettyString(shooter):user} hit {ToPrettyString(result.Value.HitEntity):target}" - + $" using {ToPrettyString(args.Gun):entity}."); - } - - var data = new HitscanRaycastFiredData - { - ShotDirection = args.ShotDirection, - Gun = args.Gun, - Shooter = args.Shooter, - HitEntity = result?.HitEntity, - }; - - var attemptEvent = new AttemptHitscanRaycastFiredEvent { Data = data }; - RaiseLocalEvent(ent, ref attemptEvent); - - if (attemptEvent.Cancelled) - return; - - var hitEvent = new HitscanRaycastFiredEvent { Data = data }; - RaiseLocalEvent(ent, ref hitEvent); - } - - /// - /// Create visual effects for the fired hitscan weapon. - /// - /// Location to start the effect. - /// Distance of the hitscan shot. - /// Angle of the shot. - /// The hitscan entity itself. - private void FireEffects(EntityCoordinates fromCoordinates, float distance, Angle shotAngle, EntityUid hitscanUid) - { - if (distance == 0 || !_visualsQuery.TryComp(hitscanUid, out var vizComp)) - return; - - var sprites = new List<(NetCoordinates coordinates, Angle angle, SpriteSpecifier sprite, float scale)>(); - var fromXform = Transform(fromCoordinates.EntityId); - - // We'll get the effects relative to the grid / map of the firer - // Look you could probably optimise this a bit with redundant transforms at this point. - - var gridUid = fromXform.GridUid; - if (gridUid != fromCoordinates.EntityId && TryComp(gridUid, out TransformComponent? gridXform)) - { - var (_, gridRot, gridInvMatrix) = _transform.GetWorldPositionRotationInvMatrix(gridXform); - var map = _transform.ToMapCoordinates(fromCoordinates); - fromCoordinates = new EntityCoordinates(gridUid.Value, Vector2.Transform(map.Position, gridInvMatrix)); - shotAngle -= gridRot; - } - else - { - shotAngle -= _transform.GetWorldRotation(fromXform); - } - - if (distance >= 1f) - { - if (vizComp.MuzzleFlash != null) - { - var coords = fromCoordinates.Offset(shotAngle.ToVec().Normalized() / 2); - var netCoords = GetNetCoordinates(coords); - - sprites.Add((netCoords, shotAngle, vizComp.MuzzleFlash, 1f)); - } - - if (vizComp.TravelFlash != null) - { - var coords = fromCoordinates.Offset(shotAngle.ToVec() * (distance + 0.5f) / 2); - var netCoords = GetNetCoordinates(coords); - - sprites.Add((netCoords, shotAngle, vizComp.TravelFlash, distance - 1.5f)); - } - } - - if (vizComp.ImpactFlash != null) - { - var coords = fromCoordinates.Offset(shotAngle.ToVec() * distance); - var netCoords = GetNetCoordinates(coords); - - sprites.Add((netCoords, shotAngle.FlipPositive(), vizComp.ImpactFlash, 1f)); - } - - if (sprites.Count > 0) - { - RaiseNetworkEvent(new SharedGunSystem.HitscanEvent - { - Sprites = sprites, - }, Filter.Pvs(fromCoordinates, entityMan: EntityManager)); - } - } -} diff --git a/Content.Shared/Weapons/Hitscan/Systems/HitscanReflectSystem.cs b/Content.Shared/Weapons/Hitscan/Systems/HitscanReflectSystem.cs deleted file mode 100644 index 389e944942..0000000000 --- a/Content.Shared/Weapons/Hitscan/Systems/HitscanReflectSystem.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Content.Shared.Weapons.Hitscan.Components; -using Content.Shared.Weapons.Hitscan.Events; -using Content.Shared.Weapons.Ranged.Events; -using Content.Shared.Weapons.Reflect; -using Robust.Shared.Random; - -namespace Content.Shared.Weapons.Hitscan.Systems; - -public sealed class HitscanReflectSystem : EntitySystem -{ - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnHitscanHit); - } - - private void OnHitscanHit(Entity hitscan, ref AttemptHitscanRaycastFiredEvent args) - { - var data = args.Data; - - if (hitscan.Comp.ReflectiveType == ReflectType.None || data.HitEntity == null) - return; - - if (hitscan.Comp.CurrentReflections >= hitscan.Comp.MaxReflections) - return; - - var ev = new HitScanReflectAttemptEvent(data.Shooter ?? data.Gun, data.Gun, hitscan.Comp.ReflectiveType, data.ShotDirection, false); - RaiseLocalEvent(data.HitEntity.Value, ref ev); - - if (!ev.Reflected) - return; - - hitscan.Comp.CurrentReflections++; - - args.Cancelled = true; - - var fromEffect = Transform(data.HitEntity.Value).Coordinates; - - var hitFiredEvent = new HitscanTraceEvent - { - FromCoordinates = fromEffect, - ShotDirection = ev.Direction, - Gun = data.Gun, - Shooter = data.HitEntity.Value, - }; - - RaiseLocalEvent(hitscan, ref hitFiredEvent); - } -} diff --git a/Content.Shared/Weapons/Hitscan/Systems/HitscanStunSystem.cs b/Content.Shared/Weapons/Hitscan/Systems/HitscanStunSystem.cs deleted file mode 100644 index 8c1d1b45fb..0000000000 --- a/Content.Shared/Weapons/Hitscan/Systems/HitscanStunSystem.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Content.Shared.Damage.Systems; -using Content.Shared.Weapons.Hitscan.Components; -using Content.Shared.Weapons.Hitscan.Events; - -namespace Content.Shared.Weapons.Hitscan.Systems; - -public sealed class HitscanStunSystem : EntitySystem -{ - [Dependency] private readonly SharedStaminaSystem _stamina = default!; - - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnHitscanHit); - } - - private void OnHitscanHit(Entity hitscan, ref HitscanRaycastFiredEvent args) - { - if (args.Data.HitEntity == null) - return; - - _stamina.TakeStaminaDamage(args.Data.HitEntity.Value, hitscan.Comp.StaminaDamage, source: args.Data.Shooter ?? args.Data.Gun); - } -} diff --git a/Content.Shared/Weapons/Ranged/Components/HitscanBatteryAmmoProviderComponent.cs b/Content.Shared/Weapons/Ranged/Components/HitscanBatteryAmmoProviderComponent.cs index cdbf51456e..be9d9805a8 100644 --- a/Content.Shared/Weapons/Ranged/Components/HitscanBatteryAmmoProviderComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/HitscanBatteryAmmoProviderComponent.cs @@ -1,11 +1,11 @@ using Robust.Shared.GameStates; -using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Weapons.Ranged.Components; [RegisterComponent, NetworkedComponent] public sealed partial class HitscanBatteryAmmoProviderComponent : BatteryAmmoProviderComponent { - [DataField("proto", required: true)] - public EntProtoId HitscanEntityProto; + [ViewVariables(VVAccess.ReadWrite), DataField("proto", required: true, customTypeSerializer: typeof(PrototypeIdSerializer))] + public string Prototype = default!; } diff --git a/Content.Shared/Weapons/Ranged/HitscanPrototype.cs b/Content.Shared/Weapons/Ranged/HitscanPrototype.cs new file mode 100644 index 0000000000..3e0c15b879 --- /dev/null +++ b/Content.Shared/Weapons/Ranged/HitscanPrototype.cs @@ -0,0 +1,57 @@ +using Content.Shared.Damage; +using Content.Shared.Physics; +using Content.Shared.Weapons.Reflect; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Shared.Weapons.Ranged; + +[Prototype] +public sealed partial class HitscanPrototype : IPrototype, IShootable +{ + [ViewVariables] + [IdDataField] + public string ID { get; private set; } = default!; + + [ViewVariables(VVAccess.ReadWrite), DataField("staminaDamage")] + public float StaminaDamage; + + [ViewVariables(VVAccess.ReadWrite), DataField("damage")] + public DamageSpecifier? Damage; + + [ViewVariables(VVAccess.ReadOnly), DataField("muzzleFlash")] + public SpriteSpecifier? MuzzleFlash; + + [ViewVariables(VVAccess.ReadOnly), DataField("travelFlash")] + public SpriteSpecifier? TravelFlash; + + [ViewVariables(VVAccess.ReadOnly), DataField("impactFlash")] + public SpriteSpecifier? ImpactFlash; + + [DataField("collisionMask")] + public int CollisionMask = (int) CollisionGroup.Opaque; + + /// + /// What we count as for reflection. + /// + [DataField("reflective")] public ReflectType Reflective = ReflectType.Energy; + + /// + /// Sound that plays upon the thing being hit. + /// + [DataField("sound")] + public SoundSpecifier? Sound; + + /// + /// Force the hitscan sound to play rather than potentially playing the entity's sound. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("forceSound")] + public bool ForceSound; + + /// + /// Try not to set this too high. + /// + [DataField("maxLength")] + public float MaxLength = 20f; +} diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs index 663f5f1faa..cb4e42f3cf 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs @@ -2,7 +2,6 @@ using Content.Shared.Damage; using Content.Shared.Damage.Events; using Content.Shared.Examine; using Content.Shared.Projectiles; -using Content.Shared.Weapons.Hitscan.Components; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; using Robust.Shared.GameStates; @@ -81,10 +80,11 @@ public abstract partial class SharedGunSystem { if (component is ProjectileBatteryAmmoProviderComponent battery) { - if (ProtoManager.Index(battery.Prototype).Components + if (ProtoManager.Index(battery.Prototype) + .Components .TryGetValue(Factory.GetComponentName(), out var projectile)) { - var p = (ProjectileComponent) projectile.Component; + var p = (ProjectileComponent)projectile.Component; if (!p.Damage.Empty) { @@ -97,11 +97,8 @@ public abstract partial class SharedGunSystem if (component is HitscanBatteryAmmoProviderComponent hitscan) { - var dmg = ProtoManager.Index(hitscan.HitscanEntityProto); - if (!dmg.TryGetComponent(out var basicDamageComp, Factory)) - return null; - - return basicDamageComp.Damage * Damageable.UniversalHitscanDamageModifier; + var dmg = ProtoManager.Index(hitscan.Prototype).Damage; + return dmg == null ? dmg : dmg * Damageable.UniversalHitscanDamageModifier; } return null; @@ -158,8 +155,7 @@ public abstract partial class SharedGunSystem var ent = Spawn(proj.Prototype, coordinates); return (ent, EnsureShootable(ent)); case HitscanBatteryAmmoProviderComponent hitscan: - var hitscanEnt = Spawn(hitscan.HitscanEntityProto); - return (hitscanEnt, EnsureShootable(hitscanEnt)); + return (null, ProtoManager.Index(hitscan.Prototype)); default: throw new ArgumentOutOfRangeException(); } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index cf6b38ee5c..f9f2fee2fe 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -21,7 +21,6 @@ using Content.Shared.Tag; using Content.Shared.Throwing; using Content.Shared.Timing; using Content.Shared.Verbs; -using Content.Shared.Weapons.Hitscan.Components; using Content.Shared.Weapons.Melee; using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Ranged.Components; @@ -605,9 +604,6 @@ public abstract partial class SharedGunSystem : EntitySystem if (TryComp(uid, out var cartridge)) return cartridge; - if (TryComp(uid, out var hitscanAmmo)) - return hitscanAmmo; - return EnsureComp(uid); } @@ -722,8 +718,6 @@ public abstract partial class SharedGunSystem : EntitySystem protected abstract void CreateEffect(EntityUid gunUid, MuzzleFlashEvent message, EntityUid? user = null); - public abstract void PlayImpactSound(EntityUid otherEntity, DamageSpecifier? modifiedDamage, SoundSpecifier? weaponSound, bool forceWeaponSound); - /// /// Used for animated effects on the client. /// diff --git a/Content.Shared/Weapons/Reflect/ReflectComponent.cs b/Content.Shared/Weapons/Reflect/ReflectComponent.cs index db68e2b2d2..1bb7dcb8fa 100644 --- a/Content.Shared/Weapons/Reflect/ReflectComponent.cs +++ b/Content.Shared/Weapons/Reflect/ReflectComponent.cs @@ -61,10 +61,6 @@ public sealed partial class ReflectComponent : Component // Sunrise-End } -/// -/// Used for both the projectiles being reflected and the entities reflecting. If there is ever overlap between the -/// reflection types, the projectile will be reflected. -/// [Flags, Serializable, NetSerializable] public enum ReflectType : byte { diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml index 003c9cadab..47f2dc2ef6 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/hitscan.yml @@ -17,145 +17,125 @@ - Laser - type: AnimationPlayer -- type: entity - id: BasicHitscan - categories: [ HideSpawnMenu ] - components: - - type: HitscanAmmo - - type: HitscanBasicRaycast - - type: HitscanBasicVisuals - muzzleFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: muzzle_laser - travelFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: beam - impactFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: impact_laser - - type: HitscanReflect - - type: HitscanBasicEffects - -- type: entity - parent: BasicHitscan - id: RedLightLaser - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 7 - -- type: entity - parent: BasicHitscan +- type: hitscan id: RedLaser - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 14 + damage: + types: + Heat: 14 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_laser + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_laser -- type: entity - parent: BasicHitscan - id: RedMediumLaser - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 17 - -- type: entity - parent: BasicHitscan - id: RedHeavyLaser - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 28 - - type: HitscanBasicVisuals - muzzleFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: muzzle_beam_heavy - travelFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: beam_heavy - impactFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: impact_beam_heavy - -- type: entity - parent: BasicHitscan +- type: hitscan id: RedLaserPractice - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 1 + damage: + types: + Heat: 1 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_laser + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_laser -- type: entity - parent: BasicHitscan +- type: hitscan + id: RedMediumLaser + damage: + types: + Heat: 17 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_laser + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_laser + +- type: hitscan + id: RedLightLaser + damage: + types: + Heat: 7 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_laser + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_laser + +- type: hitscan id: XrayLaser - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 10 - Radiation: 10 - - type: HitscanBasicVisuals - muzzleFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: muzzle_xray - travelFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: xray - impactFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: impact_xray + damage: + types: + Heat: 10 + Radiation: 10 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_xray + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: xray + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_xray -- type: entity - parent: BasicHitscan +- type: hitscan + id: RedHeavyLaser + damage: + types: + Heat: 28 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_beam_heavy + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam_heavy + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_beam_heavy + +- type: hitscan id: Pulse - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicDamage - damage: - types: - Heat: 35 - - type: HitscanBasicVisuals - muzzleFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: muzzle_blue - travelFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: beam_blue - impactFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: impact_blue + damage: + types: + Heat: 35 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_blue + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam_blue + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_blue -- type: entity - parent: BasicHitscan +- type: hitscan id: RedShuttleLaser - categories: [ HideSpawnMenu ] - components: - - type: HitscanBasicRaycast - maxDistance: 60.0 - - type: HitscanBasicDamage - damage: - types: - Heat: 45 - Structural: 10 - - type: HitscanBasicVisuals - muzzleFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: muzzle_beam_heavy2 - travelFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: beam_heavy2 - impactFlash: - sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi - state: impact_beam_heavy2 + maxLength: 60 + damage: + types: + Heat: 45 + Structural: 10 + muzzleFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: muzzle_beam_heavy2 + travelFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: beam_heavy2 + impactFlash: + sprite: Objects/Weapons/Guns/Projectiles/projectiles.rsi + state: impact_beam_heavy2