From 5107bc3be78c5e400f744bf246b6db5051abf808 Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Sat, 9 Jul 2022 13:46:11 +1000 Subject: [PATCH] Bullet impact effects (#9530) --- .../Projectiles/ProjectileComponent.cs | 13 +--- .../Projectiles/ProjectileSystem.cs | 61 +++++++++++++++++++ .../Components/ParticleProjectileComponent.cs | 4 +- .../Components/ProjectileComponent.cs | 17 ------ .../Projectiles/SharedProjectileSystem.cs | 34 ++++------- .../EntitySystems/EmitterSystem.cs | 3 +- .../StationEvents/Events/MeteorSwarm.cs | 3 +- .../Weapon/Ranged/Systems/GunSystem.cs | 4 +- .../Projectiles/SharedProjectileComponent.cs | 23 +++---- .../Projectiles/SharedProjectileSystem.cs | 36 +++++++++++ .../Weapons/Ranged/Systems/SharedGunSystem.cs | 8 ++- .../Weapons/Guns/Projectiles/impacts.yml | 32 ++++++++++ .../Weapons/Guns/Projectiles/projectiles.yml | 4 ++ .../Projectiles/projectiles_tg.rsi/meta.json | 18 +++--- 14 files changed, 175 insertions(+), 85 deletions(-) create mode 100644 Content.Client/Projectiles/ProjectileSystem.cs create mode 100644 Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/impacts.yml diff --git a/Content.Client/Projectiles/ProjectileComponent.cs b/Content.Client/Projectiles/ProjectileComponent.cs index 40730765af..d0fa12d50b 100644 --- a/Content.Client/Projectiles/ProjectileComponent.cs +++ b/Content.Client/Projectiles/ProjectileComponent.cs @@ -1,19 +1,8 @@ using Content.Shared.Projectiles; -using Robust.Shared.GameObjects; namespace Content.Client.Projectiles { [RegisterComponent] [ComponentReference(typeof(SharedProjectileComponent))] - public sealed class ProjectileComponent : SharedProjectileComponent - { - public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) - { - if (curState is ProjectileComponentState compState) - { - Shooter = compState.Shooter; - IgnoreShooter = compState.IgnoreShooter; - } - } - } + public sealed class ProjectileComponent : SharedProjectileComponent {} } diff --git a/Content.Client/Projectiles/ProjectileSystem.cs b/Content.Client/Projectiles/ProjectileSystem.cs new file mode 100644 index 0000000000..1869d7a690 --- /dev/null +++ b/Content.Client/Projectiles/ProjectileSystem.cs @@ -0,0 +1,61 @@ +using Content.Shared.Projectiles; +using Content.Shared.Spawners.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Shared.GameStates; + +namespace Content.Client.Projectiles; + +public sealed class ProjectileSystem : SharedProjectileSystem +{ + [Dependency] private readonly AnimationPlayerSystem _player = default!; + + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnHandleState); + SubscribeNetworkEvent(OnProjectileImpact); + } + + private void OnProjectileImpact(ImpactEffectEvent ev) + { + var ent = Spawn(ev.Prototype, ev.Coordinates); + + if (TryComp(ent, out var sprite)) + { + sprite[EffectLayers.Unshaded].AutoAnimated = false; + sprite.LayerMapTryGet(EffectLayers.Unshaded, out var layer); + var state = sprite.LayerGetState(layer); + var lifetime = 0.5f; + + if (TryComp(ent, out var despawn)) + lifetime = despawn.Lifetime; + + var anim = new Animation() + { + Length = TimeSpan.FromSeconds(lifetime), + AnimationTracks = + { + new AnimationTrackSpriteFlick() + { + LayerKey = EffectLayers.Unshaded, + KeyFrames = + { + new AnimationTrackSpriteFlick.KeyFrame(state.Name, 0f), + } + } + } + }; + + _player.Play(ent, anim, "impact-effect"); + } + } + + private void OnHandleState(EntityUid uid, ProjectileComponent component, ref ComponentHandleState args) + { + if (args.Current is not ProjectileComponentState state) return; + component.Shooter = state.Shooter; + component.IgnoreShooter = state.IgnoreShooter; + } +} diff --git a/Content.Server/ParticleAccelerator/Components/ParticleProjectileComponent.cs b/Content.Server/ParticleAccelerator/Components/ParticleProjectileComponent.cs index 91a1d67db7..0fb3fe18b9 100644 --- a/Content.Server/ParticleAccelerator/Components/ParticleProjectileComponent.cs +++ b/Content.Server/ParticleAccelerator/Components/ParticleProjectileComponent.cs @@ -1,3 +1,4 @@ +using Content.Server.Projectiles; using Content.Server.Projectiles.Components; using Content.Server.Singularity.Components; using Content.Shared.Singularity.Components; @@ -29,7 +30,8 @@ namespace Content.Server.ParticleAccelerator.Components Logger.Error("ParticleProjectile tried firing, but it was spawned without a ProjectileComponent"); return; } - projectileComponent.IgnoreEntity(firer); + + _entMan.EntitySysManager.GetEntitySystem().SetShooter(projectileComponent, firer); if (!_entMan.TryGetComponent(Owner, out var singuloFoodComponent)) { diff --git a/Content.Server/Projectiles/Components/ProjectileComponent.cs b/Content.Server/Projectiles/Components/ProjectileComponent.cs index da5ba7a442..59e9a20800 100644 --- a/Content.Server/Projectiles/Components/ProjectileComponent.cs +++ b/Content.Server/Projectiles/Components/ProjectileComponent.cs @@ -22,22 +22,5 @@ namespace Content.Server.Projectiles.Components public bool ForceSound = false; public bool DamagedEntity; - - public float TimeLeft { get; set; } = 10; - - /// - /// Function that makes the collision of this object ignore a specific entity so we don't collide with ourselves - /// - /// - public void IgnoreEntity(EntityUid shooter) - { - Shooter = shooter; - Dirty(); - } - - public override ComponentState GetComponentState() - { - return new ProjectileComponentState(Shooter, IgnoreShooter); - } } } diff --git a/Content.Server/Projectiles/SharedProjectileSystem.cs b/Content.Server/Projectiles/SharedProjectileSystem.cs index 3d9bd47922..9cb0cb251f 100644 --- a/Content.Server/Projectiles/SharedProjectileSystem.cs +++ b/Content.Server/Projectiles/SharedProjectileSystem.cs @@ -1,19 +1,13 @@ using Content.Server.Administration.Logs; using Content.Server.Projectiles.Components; -using Content.Server.Weapon.Melee; -using Content.Server.Weapon.Ranged; -using Content.Shared.Audio; -using Content.Shared.Body.Components; using Content.Shared.Camera; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Projectiles; using JetBrains.Annotations; using Robust.Server.GameObjects; -using Robust.Shared.Audio; +using Robust.Shared.GameStates; using Robust.Shared.Physics.Dynamics; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; using GunSystem = Content.Server.Weapon.Ranged.Systems.GunSystem; namespace Content.Server.Projectiles @@ -29,16 +23,20 @@ namespace Content.Server.Projectiles public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(HandleCollide); + SubscribeLocalEvent(OnStartCollide); + SubscribeLocalEvent(OnGetState); } - private void HandleCollide(EntityUid uid, ProjectileComponent component, StartCollideEvent args) + private void OnGetState(EntityUid uid, ProjectileComponent component, ref ComponentGetState args) + { + args.State = new ProjectileComponentState(component.Shooter, component.IgnoreShooter); + } + + private void OnStartCollide(EntityUid uid, ProjectileComponent component, StartCollideEvent args) { // This is so entities that shouldn't get a collision are ignored. if (args.OurFixture.ID != ProjectileFixture || !args.OtherFixture.Hard || component.DamagedEntity) - { return; - } var otherEntity = args.OtherFixture.Body.Owner; @@ -62,20 +60,12 @@ namespace Content.Server.Projectiles } if (component.DeleteOnCollide) - QueueDel(uid); - } - - public override void Update(float frameTime) - { - base.Update(frameTime); - - foreach (var component in EntityManager.EntityQuery()) { - component.TimeLeft -= frameTime; + QueueDel(uid); - if (component.TimeLeft <= 0) + if (component.ImpactEffect != null && TryComp(uid, out var xform)) { - EntityManager.DeleteEntity(component.Owner); + RaiseNetworkEvent(new ImpactEffectEvent(component.ImpactEffect, xform.Coordinates)); } } } diff --git a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs index 71d180d67d..fca28c7f00 100644 --- a/Content.Server/Singularity/EntitySystems/EmitterSystem.cs +++ b/Content.Server/Singularity/EntitySystems/EmitterSystem.cs @@ -2,6 +2,7 @@ using System.Threading; using Content.Server.Administration.Logs; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; +using Content.Server.Projectiles; using Content.Server.Projectiles.Components; using Content.Server.Singularity.Components; using Content.Server.Storage.Components; @@ -187,7 +188,7 @@ namespace Content.Server.Singularity.EntitySystems return; } - projectileComponent.IgnoreEntity(component.Owner); + Get().SetShooter(projectileComponent, component.Owner); physicsComponent .LinearVelocity = EntityManager.GetComponent(component.Owner).WorldRotation.ToWorldVec() * 20f; diff --git a/Content.Server/StationEvents/Events/MeteorSwarm.cs b/Content.Server/StationEvents/Events/MeteorSwarm.cs index df369bf7a7..b5167856c6 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarm.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarm.cs @@ -1,6 +1,7 @@ using Content.Server.GameTicking; using Content.Server.Projectiles.Components; using Content.Shared.Sound; +using Content.Shared.Spawners.Components; using Robust.Shared.Map; using Robust.Shared.Random; @@ -116,7 +117,7 @@ namespace Content.Server.StationEvents.Events physics.Mass * ((MaxAngularVelocity - MinAngularVelocity) * _robustRandom.NextFloat() + MinAngularVelocity)); // TODO: God this disgusts me but projectile needs a refactor. - IoCManager.Resolve().GetComponent(meteor).TimeLeft = 120f; + IoCManager.Resolve().EnsureComponent(meteor).Lifetime = 120f; } } } diff --git a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs index 8d4405bd7c..b8e4915580 100644 --- a/Content.Server/Weapon/Ranged/Systems/GunSystem.cs +++ b/Content.Server/Weapon/Ranged/Systems/GunSystem.cs @@ -7,7 +7,6 @@ using Content.Shared.Audio; using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Sound; -using Content.Shared.Throwing; using Content.Shared.Weapons.Ranged; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Events; @@ -16,7 +15,6 @@ using Robust.Shared.Audio; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Player; -using Robust.Shared.Random; using Robust.Shared.Utility; using SharedGunSystem = Content.Shared.Weapons.Ranged.Systems.SharedGunSystem; @@ -168,7 +166,7 @@ public sealed partial class GunSystem : SharedGunSystem if (user != null) { var projectile = EnsureComp(uid); - projectile.IgnoreEntity(user.Value); + Projectiles.SetShooter(projectile, user.Value); } Transform(uid).WorldRotation = direction.ToWorldAngle(); diff --git a/Content.Shared/Projectiles/SharedProjectileComponent.cs b/Content.Shared/Projectiles/SharedProjectileComponent.cs index 99f52935ff..f0d7b2f926 100644 --- a/Content.Shared/Projectiles/SharedProjectileComponent.cs +++ b/Content.Shared/Projectiles/SharedProjectileComponent.cs @@ -1,13 +1,17 @@ using Robust.Shared.GameStates; -using Robust.Shared.Serialization; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Projectiles { - [NetworkedComponent()] + [NetworkedComponent, Access(typeof(SharedProjectileSystem))] public abstract class SharedProjectileComponent : Component { + [ViewVariables(VVAccess.ReadWrite), DataField("impactEffect", customTypeSerializer:typeof(PrototypeIdSerializer))] + public string? ImpactEffect; + private bool _ignoreShooter = true; - public EntityUid Shooter { get; protected set; } + public EntityUid Shooter { get; set; } public bool IgnoreShooter { @@ -20,18 +24,5 @@ namespace Content.Shared.Projectiles Dirty(); } } - - [NetSerializable, Serializable] - protected sealed class ProjectileComponentState : ComponentState - { - public ProjectileComponentState(EntityUid shooter, bool ignoreShooter) - { - Shooter = shooter; - IgnoreShooter = ignoreShooter; - } - - public EntityUid Shooter { get; } - public bool IgnoreShooter { get; } - } } } diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index 52b322302e..99c1c37f4f 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -1,4 +1,6 @@ +using Robust.Shared.Map; using Robust.Shared.Physics.Dynamics; +using Robust.Shared.Serialization; namespace Content.Shared.Projectiles { @@ -20,5 +22,39 @@ namespace Content.Shared.Projectiles return; } } + + public void SetShooter(SharedProjectileComponent component, EntityUid uid) + { + if (component.Shooter == uid) return; + + component.Shooter = uid; + Dirty(component); + } + + [NetSerializable, Serializable] + protected sealed class ProjectileComponentState : ComponentState + { + public ProjectileComponentState(EntityUid shooter, bool ignoreShooter) + { + Shooter = shooter; + IgnoreShooter = ignoreShooter; + } + + public EntityUid Shooter { get; } + public bool IgnoreShooter { get; } + } + + [Serializable, NetSerializable] + protected sealed class ImpactEffectEvent : EntityEventArgs + { + public string Prototype; + public EntityCoordinates Coordinates; + + public ImpactEffectEvent(string prototype, EntityCoordinates coordinates) + { + Prototype = prototype; + Coordinates = coordinates; + } + } } } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index d98ccb0d4f..af2e31d311 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.Examine; using Content.Shared.Hands.Components; using Content.Shared.Interaction.Events; using Content.Shared.Popups; +using Content.Shared.Projectiles; using Content.Shared.Throwing; using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Components; @@ -43,7 +44,8 @@ public abstract partial class SharedGunSystem : EntitySystem [Dependency] protected readonly SharedPhysicsSystem Physics = default!; [Dependency] protected readonly SharedPopupSystem PopupSystem = default!; [Dependency] protected readonly ThrowingSystem ThrowingSystem = default!; - [Dependency] protected readonly TagSystem _tagSystem = default!; + [Dependency] protected readonly TagSystem TagSystem = default!; + [Dependency] protected readonly SharedProjectileSystem Projectiles = default!; protected ISawmill Sawmill = default!; @@ -88,7 +90,7 @@ public abstract partial class SharedGunSystem : EntitySystem private void OnGunMeleeAttempt(EntityUid uid, GunComponent component, ref MeleeAttackAttemptEvent args) { - if (_tagSystem.HasTag(args.User, "GunsDisabled")) + if (TagSystem.HasTag(args.User, "GunsDisabled")) return; args.Cancelled = true; @@ -186,7 +188,7 @@ public abstract partial class SharedGunSystem : EntitySystem if (toCoordinates == null) return; - if (_tagSystem.HasTag(user, "GunsDisabled")) + if (TagSystem.HasTag(user, "GunsDisabled")) { Popup(Loc.GetString("gun-disabled"), user, user); return; diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/impacts.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/impacts.yml new file mode 100644 index 0000000000..c502df7402 --- /dev/null +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/impacts.yml @@ -0,0 +1,32 @@ +- type: entity + id: BulletImpactEffect + noSpawn: true + components: + - type: TimedDespawn + lifetime: 0.25 + - type: Sprite + netsync: false + drawdepth: Effects + layers: + - shader: unshaded + map: ["enum.EffectLayers.Unshaded"] + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + state: impact_bullet + - type: EffectVisuals + +- type: entity + id: BulletImpactEffectDisabler + noSpawn: true + components: + - type: TimedDespawn + lifetime: 0.2 + - type: Sprite + netsync: false + drawdepth: Effects + layers: + - shader: unshaded + map: ["enum.EffectLayers.Unshaded"] + sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi + state: impact_laser_blue + - type: EffectVisuals + diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index e10582ade1..e2bb66b1a5 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -29,11 +29,14 @@ - Impassable - BulletImpassable - type: Projectile + impactEffect: BulletImpactEffect damage: types: Piercing: 14 soundHit: path: /Audio/Weapons/Guns/Hits/bullet_hit.ogg + - type: TimedDespawn + lifetime: 10 - type: entity id: BaseBulletTrigger # Trigger-on-collide bullets @@ -126,6 +129,7 @@ - type: StaminaDamageOnCollide damage: 30 - type: Projectile + impactEffect: BulletImpactEffectDisabler damage: types: Heat: 5 diff --git a/Resources/Textures/Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi/meta.json index f6b561e1de..7cc3964b24 100644 --- a/Resources/Textures/Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi/meta.json +++ b/Resources/Textures/Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi/meta.json @@ -14,11 +14,11 @@ "name": "impact_bullet", "delays": [ [ - 0.1, - 0.1, - 0.1, - 0.1, - 0.1 + 0.05, + 0.05, + 0.05, + 0.05, + 0.05 ] ] }, @@ -26,10 +26,10 @@ "name": "impact_laser_blue", "delays": [ [ - 0.1, - 0.1, - 0.1, - 0.1 + 0.05, + 0.05, + 0.05, + 0.05 ] ] }