diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs index f42c2d27fb..d47d040259 100644 --- a/Content.Client/Entry/EntryPoint.cs +++ b/Content.Client/Entry/EntryPoint.cs @@ -1,3 +1,5 @@ +using Content.Client._RMC14.Explosion; +using Content.Client._RMC14.Xenonids.Screech; using Content.Client._Sunrise.Entry; using Content.Client._Sunrise.ServersHub; using Content.Client.Administration.Managers; @@ -174,6 +176,10 @@ namespace Content.Client.Entry _parallaxManager.LoadDefaultParallax(); _overlayManager.AddOverlay(new SingularityOverlay()); + // Sunrise edit start + _overlayManager.AddOverlay(new RMCExplosionShockWaveOverlay()); + _overlayManager.AddOverlay(new RMCXenoScreechShockWaveOverlay()); + // Sunrise edit end _overlayManager.AddOverlay(new RadiationPulseOverlay()); _chatManager.Initialize(); _clientPreferencesManager.Initialize(); diff --git a/Content.Client/_RMC14/Explosion/RMCExplosionShockWaveOverlay.cs b/Content.Client/_RMC14/Explosion/RMCExplosionShockWaveOverlay.cs new file mode 100644 index 0000000000..c408fc6ec6 --- /dev/null +++ b/Content.Client/_RMC14/Explosion/RMCExplosionShockWaveOverlay.cs @@ -0,0 +1,101 @@ +using System.Numerics; +using Content.Shared._RMC14.Explosion.Components; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; + +namespace Content.Client._RMC14.Explosion; + +public sealed class RMCExplosionShockWaveOverlay : Overlay, IEntityEventSubscriber +{ + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IGameTiming _timing = default!; + + private SharedTransformSystem? _xformSystem; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + + private readonly ShaderInstance _shader; + + /// + /// Maximum number of distortions that can be shown on screen at a time. + /// + public const int MaxCount = 10; + + public RMCExplosionShockWaveOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototypeManager.Index("RMCShockWave").Instance().Duplicate(); + } + + private readonly Vector2[] _positions = new Vector2[MaxCount]; + private readonly float[] _falloffPower = new float[MaxCount]; + private readonly float[] _sharpness = new float[MaxCount]; + private readonly float[] _width = new float[MaxCount]; + private readonly float[] _times = new float[MaxCount]; + private int _count; + + private readonly TimeSpan _timeCompensation = TimeSpan.FromSeconds(0.2f); + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (args.Viewport.Eye == null || _xformSystem is null && !_entMan.TrySystem(out _xformSystem)) + return false; + + var query = _entMan.EntityQueryEnumerator(); + + _count = 0; + + while (query.MoveNext(out var uid, out var distortion, out var xform)) + { + if (xform.MapID != args.MapId) + continue; + + var mapPos = _xformSystem.GetWorldPosition(uid); + + var tempCoords = args.Viewport.WorldToLocal(mapPos); + + // normalized coords, 0 - 1 plane. This is pure hell, we subtract 1 because fragment calculates from the bottom and local goes from the top of the viewport + tempCoords.Y = 1 - (tempCoords.Y / args.Viewport.Size.Y); + tempCoords.X /= args.Viewport.Size.X; + + var currentTime = (float)(_timing.CurTime - distortion.CreationTime - _timeCompensation).TotalSeconds; + currentTime = Math.Clamp(currentTime, 0.1f, float.MaxValue); + + _positions[_count] = tempCoords; + _falloffPower[_count] = distortion.FalloffPower ?? 20f; // Sunrise edit - фолбек + _sharpness[_count] = distortion.Sharpness; + _width[_count] = distortion.Width ?? 0.8f; + _times[_count] = currentTime; + _count++; + + if (_count == MaxCount) + break; + } + + return _count > 0; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null || args.Viewport.Eye == null) + return; + + _shader?.SetParameter("renderScale", args.Viewport.RenderScale * args.Viewport.Eye.Scale); + _shader?.SetParameter("count", _count); + _shader?.SetParameter("position", _positions); + _shader?.SetParameter("falloffPower", _falloffPower); + _shader?.SetParameter("sharpness", _sharpness); + _shader?.SetParameter("width", _width); + _shader?.SetParameter("time", _times); + _shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + var worldHandle = args.WorldHandle; + worldHandle.UseShader(_shader); + worldHandle.DrawRect(args.WorldBounds, Color.White); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/_RMC14/Xenonids/Screech/RMCXenoScreechShockWaveOverlay.cs b/Content.Client/_RMC14/Xenonids/Screech/RMCXenoScreechShockWaveOverlay.cs new file mode 100644 index 0000000000..daaa8fe312 --- /dev/null +++ b/Content.Client/_RMC14/Xenonids/Screech/RMCXenoScreechShockWaveOverlay.cs @@ -0,0 +1,77 @@ +using System.Numerics; +using Content.Shared._RMC14.Xenonids.Screech; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; + +namespace Content.Client._RMC14.Xenonids.Screech; + +public sealed class RMCXenoScreechShockWaveOverlay : Overlay, IEntityEventSubscriber +{ + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + + private SharedTransformSystem? _xformSystem; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public override bool RequestScreenTexture => true; + + private readonly ShaderInstance _shader; + + public RMCXenoScreechShockWaveOverlay() + { + IoCManager.InjectDependencies(this); + _shader = _prototypeManager.Index("RMCXenoScreechShockWave").Instance().Duplicate(); + } + + private Vector2 _position; + private float _waveStrength; + private float _waveSpeed; + private float _downScale; + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (args.Viewport.Eye == null || _xformSystem is null && !_entMan.TrySystem(out _xformSystem)) + return false; + + var query = _entMan.EntityQueryEnumerator(); + + if (query.MoveNext(out var uid, out var distortion, out var xform)) + { + if (xform.MapID != args.MapId) + return false; + + var mapPos = _xformSystem.GetWorldPosition(uid); + var tempCoords = args.Viewport.WorldToLocal(mapPos); + + // normalized coords, 0 - 1 plane. This is pure hell, we subtract 1 because fragment calculates from the bottom and local goes from the top of the viewport + tempCoords.Y = 1 - (tempCoords.Y / args.Viewport.Size.Y); + tempCoords.X /= args.Viewport.Size.X; + + _position = tempCoords; + _waveStrength = distortion.WaveStrength; + _waveSpeed = distortion.WaveSpeed; + _downScale = distortion.DownScale; + + return true; + } + + return false; + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture == null || args.Viewport.Eye == null) + return; + + _shader?.SetParameter("position", _position); + _shader?.SetParameter("waveSpeed", _waveSpeed); + _shader?.SetParameter("downScale", _downScale); + _shader?.SetParameter("waveStrength", _waveStrength); + _shader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); + + var worldHandle = args.WorldHandle; + worldHandle.UseShader(_shader); + worldHandle.DrawRect(args.WorldBounds, Color.White); + worldHandle.UseShader(null); + } +} diff --git a/Content.Client/_RMC14/_Sunrise/Explosion/ClientExplosionShockWaveSystem.cs b/Content.Client/_RMC14/_Sunrise/Explosion/ClientExplosionShockWaveSystem.cs new file mode 100644 index 0000000000..063ca10db2 --- /dev/null +++ b/Content.Client/_RMC14/_Sunrise/Explosion/ClientExplosionShockWaveSystem.cs @@ -0,0 +1,21 @@ +using Content.Shared._RMC14.Explosion.Components; +using Robust.Shared.Timing; + +namespace Content.Client._RMC14._Sunrise.Explosion; + +public sealed class ClientExplosionShockWaveSystem : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + ent.Comp.CreationTime = _timing.CurTime; + } +} diff --git a/Content.Client/_RMC14/_Sunrise/Explosion/RMCExplosionSystem.cs b/Content.Client/_RMC14/_Sunrise/Explosion/RMCExplosionSystem.cs new file mode 100644 index 0000000000..f2b47ac5c8 --- /dev/null +++ b/Content.Client/_RMC14/_Sunrise/Explosion/RMCExplosionSystem.cs @@ -0,0 +1,82 @@ +using System.Numerics; +using Content.Shared._RMC14.Explosion; +using Robust.Client.Animations; +using Robust.Client.GameObjects; +using Robust.Shared.Animations; +using Robust.Shared.Random; + +namespace Content.Client._RMC14._Sunrise.Explosion; + +// Омг это же партикл систем за 1$ +public sealed class RMCExplosionSystem : SharedRMCExplosionSystem +{ + [Dependency] private readonly SpriteSystem _sprite = default!; + [Dependency] private readonly AnimationPlayerSystem _player = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + private const string SmokeTrack = "smoke-animation"; + private const string ExplosionTrack = "explosion-animation"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSmokeStartup); + SubscribeLocalEvent(OnExplosionStartup); + } + + private void OnSmokeStartup(Entity ent, ref ComponentStartup args) + { + if (!TryComp(ent, out var sprite)) + return; + + var targetX = 2f + _random.NextFloat(-ExplosionSmokeEffectComponent.Variation, ExplosionSmokeEffectComponent.Variation); + var targetY = 2f + _random.NextFloat(-ExplosionSmokeEffectComponent.Variation, ExplosionSmokeEffectComponent.Variation); + + var animation = new Animation() + { + Length = TimeSpan.FromSeconds(ent.Comp.LifeTime), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + Property = nameof(SpriteComponent.Offset), + ComponentType = typeof(SpriteComponent), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Offset, 0f), + new AnimationTrackProperty.KeyFrame(new Vector2(targetX, targetY), ent.Comp.LifeTime), + }, + }, + new AnimationTrackComponentProperty() + { + Property = nameof(SpriteComponent.Color), + ComponentType = typeof(SpriteComponent), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Color, 0f), + new AnimationTrackProperty.KeyFrame(GetTransparentColor(sprite.Color), ent.Comp.LifeTime), + }, + }, + }, + }; + + _player.Play(ent, animation, SmokeTrack); + } + + private void OnExplosionStartup(Entity ent, ref ComponentStartup args) + { + if (!TryComp(ent, out var sprite)) + return; + + sprite.Scale = new Vector2(ent.Comp.SizeModifier); + _sprite.SetAutoAnimateSync(sprite, ent.Comp.LifeTime); + } + + private static Color GetTransparentColor(Color color) + { + return new Color(color.R, color.G, color.B, 0f); + } +} diff --git a/Content.Server/Changeling/ChangelingSystem.Abilities.cs b/Content.Server/Changeling/ChangelingSystem.Abilities.cs index ec25d1127b..a1fa1a904e 100644 --- a/Content.Server/Changeling/ChangelingSystem.Abilities.cs +++ b/Content.Server/Changeling/ChangelingSystem.Abilities.cs @@ -20,6 +20,8 @@ using Content.Shared.Stealth.Components; using Content.Shared.Damage.Components; using Content.Server.Radio.Components; using Content.Shared._Sunrise.CollectiveMind; +using Content.Shared._RMC14.Xenonids.Screech; +using Content.Shared.Coordinates; namespace Content.Server.Changeling; @@ -263,6 +265,10 @@ public sealed partial class ChangelingSystem : EntitySystem _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); + // Sunrise edit start + StartScreech(uid); + // Sunrise edit end + comp.IsInStasis = false; args.Handled = true; @@ -637,4 +643,17 @@ public sealed partial class ChangelingSystem : EntitySystem } #endregion + + // Sunrise edit start + private void StartScreech(EntityUid uid, XenoScreechComponent? component = null, bool playSound = true) + { + if (!Resolve(uid, ref component)) + return; + + if (playSound) + _audio.PlayPvs(component.Sound, uid); + + SpawnAttachedTo(component.Effect, uid.ToCoordinates()); + } + // Sunrise edit end } diff --git a/Content.Server/Changeling/ChangelingSystem.cs b/Content.Server/Changeling/ChangelingSystem.cs index 2fdd9a1d27..bec0bfc385 100644 --- a/Content.Server/Changeling/ChangelingSystem.cs +++ b/Content.Server/Changeling/ChangelingSystem.cs @@ -55,6 +55,7 @@ using Content.Shared.Mobs.Components; using Content.Server.Stunnable; using Content.Shared.Jittering; using System.Linq; +using Content.Shared._RMC14.Xenonids.Screech; using Content.Shared.Forensics.Components; using Content.Shared.Radio; @@ -266,6 +267,10 @@ public sealed partial class ChangelingSystem : EntitySystem { _audio.PlayPvs(comp.ShriekSound, uid); + // Sunrise edit start + StartScreech(uid, playSound: false); + // Sunrise edit end + var center = Transform(uid).MapPosition; var gamers = Filter.Empty(); gamers.AddInRange(center, comp.ShriekPower, _player, EntityManager); @@ -607,6 +612,10 @@ public sealed partial class ChangelingSystem : EntitySystem RemComp(uid); EnsureComp(uid); + // Sunrise edit start + EnsureComp(uid); + // Sunrise edit end + // add actions foreach (var actionId in comp.BaseChangelingActions) _actions.AddAction(uid, actionId); diff --git a/Content.Server/EntityEffects/Effects/ExplosionReactionEffect.cs b/Content.Server/EntityEffects/Effects/ExplosionReactionEffect.cs index 24c58898c6..0a378ef230 100644 --- a/Content.Server/EntityEffects/Effects/ExplosionReactionEffect.cs +++ b/Content.Server/EntityEffects/Effects/ExplosionReactionEffect.cs @@ -5,6 +5,7 @@ using Content.Shared.Explosion; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using System.Text.Json.Serialization; +using Content.Shared._Sunrise.Explosion; namespace Content.Server.EntityEffects.Effects; @@ -45,7 +46,7 @@ public sealed partial class ExplosionReactionEffect : EntityEffect /// [DataField] public float IntensityPerUnit = 1; - + /// /// Factor used to scale the explosion intensity when calculating tile break chances. Allows for stronger /// explosives that don't space tiles, without having to create a new explosion-type prototype. @@ -68,6 +69,11 @@ public sealed partial class ExplosionReactionEffect : EntityEffect intensity = MathF.Min((float) reagentArgs.Quantity * IntensityPerUnit, MaxTotalIntensity); } + // Sunrise edit start + args.EntityManager.System() + .TryAddExplosionEffect(args.TargetEntity, ExplosionType); + // Sunrise edit end + args.EntityManager.System() .QueueExplosion( args.TargetEntity, diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs index 57323e4de7..f2bd914f61 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.Visuals.cs @@ -37,9 +37,15 @@ public sealed partial class ExplosionSystem /// /// Constructor for the shared using the server-exclusive explosion classes. /// - private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) + private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, ExplosionPrototype prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable gridData, List iterationIntensity) { var explosionEntity = Spawn(null, MapCoordinates.Nullspace); + + // Sunrise added start + if (prototype.EffectType != ExplosionEffectType.Standard) + return explosionEntity; + // Sunrise added end + var comp = AddComp(explosionEntity); foreach (var grid in gridData) @@ -49,7 +55,7 @@ public sealed partial class ExplosionSystem comp.SpaceTiles = spaceData?.TileLists; comp.Epicenter = epicenter; - comp.ExplosionType = prototype; + comp.ExplosionType = prototype.ID; // Sunrise edit comp.Intensity = iterationIntensity; comp.SpaceMatrix = spaceMatrix; comp.SpaceTileSize = spaceData?.TileSize ?? DefaultTileSize; diff --git a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs index beffbd6d68..43cca7a962 100644 --- a/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs +++ b/Content.Server/Explosion/EntitySystems/ExplosionSystem.cs @@ -5,6 +5,7 @@ using Content.Server.Atmos.Components; using Content.Server.Chat.Managers; using Content.Server.NodeContainer.EntitySystems; using Content.Server.NPC.Pathfinding; +using Content.Shared._RMC14.Explosion; using Content.Shared.Camera; using Content.Shared.CCVar; using Content.Shared.Damage; @@ -319,6 +320,14 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem }; _explosionQueue.Enqueue(boom); _queuedExplosions.Add(boom); + + // Sunrise added start + if (!cause.HasValue) + return; + + var ev = new CMExplosiveTriggeredEvent(); + RaiseLocalEvent(cause.Value, ref ev); + // Sunrise added end } /// @@ -339,7 +348,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem var (area, iterationIntensity, spaceData, gridData, spaceMatrix) = results.Value; - var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity); + // Sunrise edit - queued.Proto.ID -> queued.Proto + var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto, spaceMatrix, spaceData, gridData.Values, iterationIntensity); // camera shake CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity); diff --git a/Content.Server/_Sunrise/Explosions/RMCExplosionSystem.cs b/Content.Server/_Sunrise/Explosions/RMCExplosionSystem.cs new file mode 100644 index 0000000000..090c0bc683 --- /dev/null +++ b/Content.Server/_Sunrise/Explosions/RMCExplosionSystem.cs @@ -0,0 +1,8 @@ +using Content.Shared._RMC14.Explosion; + +namespace Content.Server._Sunrise.Explosions; + +public sealed class RMCExplosionSystem : SharedRMCExplosionSystem +{ + +} diff --git a/Content.Shared/Explosion/ExplosionPrototype.cs b/Content.Shared/Explosion/ExplosionPrototype.cs index df2fb18360..cbc6836249 100644 --- a/Content.Shared/Explosion/ExplosionPrototype.cs +++ b/Content.Shared/Explosion/ExplosionPrototype.cs @@ -1,6 +1,7 @@ using Content.Shared.Damage; using Robust.Shared.Audio; using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; using Robust.Shared.Utility; namespace Content.Shared.Explosion; @@ -110,6 +111,11 @@ public sealed partial class ExplosionPrototype : IPrototype [DataField("fireStates")] public int FireStates = 3; + // Sunrise added start + [DataField] + public ExplosionEffectType EffectType = ExplosionEffectType.Fancy; + // Sunrise added end + /// /// Basic function for linear interpolation of the _tileBreakChance and _tileBreakIntensity arrays /// @@ -133,3 +139,11 @@ public sealed partial class ExplosionPrototype : IPrototype return _tileBreakChance[i - 1] + slope * (intensity - _tileBreakIntensity[i - 1]); } } + +[Serializable, NetSerializable] +public enum ExplosionEffectType : byte +{ + Standard, + Fancy, +} + diff --git a/Content.Shared/Projectiles/SharedProjectileSystem.cs b/Content.Shared/Projectiles/SharedProjectileSystem.cs index be86fd1af2..5ae7a80c19 100644 --- a/Content.Shared/Projectiles/SharedProjectileSystem.cs +++ b/Content.Shared/Projectiles/SharedProjectileSystem.cs @@ -193,9 +193,9 @@ public abstract partial class SharedProjectileSystem : EntitySystem } } - public void SetShooter(EntityUid id, ProjectileComponent component, EntityUid shooterId) + public void SetShooter(EntityUid id, ProjectileComponent component, EntityUid? shooterId = null) { - if (component.Shooter == shooterId) + if (component.Shooter == shooterId || shooterId == null) return; component.Shooter = shooterId; diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 426fe84c1c..4b7af890f7 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -484,7 +484,7 @@ public abstract partial class SharedGunSystem : EntitySystem Physics.SetLinearVelocity(uid, finalLinear, body: physics); var projectile = EnsureComp(uid); - Projectiles.SetShooter(uid, projectile, user ?? gunUid); + Projectiles.SetShooter(uid, projectile, user); projectile.Weapon = gunUid; // Sunrise-Start diff --git a/Content.Shared/_RMC14/Explosion/CMExplosionEffectComponent.cs b/Content.Shared/_RMC14/Explosion/CMExplosionEffectComponent.cs new file mode 100644 index 0000000000..dcd75d6264 --- /dev/null +++ b/Content.Shared/_RMC14/Explosion/CMExplosionEffectComponent.cs @@ -0,0 +1,44 @@ +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._RMC14.Explosion; + +[RegisterComponent, NetworkedComponent] +[Access(typeof(SharedRMCExplosionSystem))] +public sealed partial class CMExplosionEffectComponent : Component +{ + [DataField] + public EntProtoId? Explosion = "CMExplosionEffectGrenade"; + + [DataField] + public EntProtoId? ShockWave = "RMCExplosionEffectGrenadeShockWave"; + + // Sunrise added + [DataField] + public EntProtoId? Smoke = "ExplosionEffectSmoke"; + // Sunrise added +} + +// Sunrise added start +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExplosionSmokeEffectComponent : Component +{ + public const float AnimationDuration = 2.5f; + public const float Variation = 1f; + + [DataField, AutoNetworkedField] + public float LifeTime = AnimationDuration; +} + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class ExplosionEffectComponent : Component +{ + public const float AnimationDuration = 2.5f; + + [DataField, AutoNetworkedField] + public float LifeTime = AnimationDuration; + + [DataField, AutoNetworkedField] + public float SizeModifier = 2f; +} +// Sunrise added end diff --git a/Content.Shared/_RMC14/Explosion/RMCExplosionShockWaveComponent.cs b/Content.Shared/_RMC14/Explosion/RMCExplosionShockWaveComponent.cs new file mode 100644 index 0000000000..c8776a1bfc --- /dev/null +++ b/Content.Shared/_RMC14/Explosion/RMCExplosionShockWaveComponent.cs @@ -0,0 +1,30 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Explosion.Components +{ + [RegisterComponent, NetworkedComponent] + [AutoGenerateComponentState] + public sealed partial class RMCExplosionShockWaveComponent : Component + { + /// + /// The rate at which the wave fades, lower values means it's active for longer. + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float? FalloffPower = 20f; + + /// + /// How sharp the wave distortion is. Higher values make the wave more pronounced. + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float Sharpness = 5.0f; + + /// + /// Width of the wave. + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float? Width = 0.8f; + + [DataField] + public TimeSpan CreationTime; + } +} diff --git a/Content.Shared/_RMC14/Explosion/SharedRMCExplosionSystem.cs b/Content.Shared/_RMC14/Explosion/SharedRMCExplosionSystem.cs new file mode 100644 index 0000000000..31cc1d5181 --- /dev/null +++ b/Content.Shared/_RMC14/Explosion/SharedRMCExplosionSystem.cs @@ -0,0 +1,122 @@ +using Content.Shared._RMC14.Explosion.Components; +using Content.Shared._Sunrise.Helpers; +using Content.Shared.Explosion.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Spawners; + +namespace Content.Shared._RMC14.Explosion; + +public abstract class SharedRMCExplosionSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + + private const float MinSmokeCountPer100 = 12f; + private const float MaxSmokeCountPer100 = 17f; + + private const float SmokeSpawnRadiusPer100 = 2f; + + public override void Initialize() + { + SubscribeLocalEvent(OnExplosionEffectTriggered); + } + + private void OnExplosionEffectTriggered(Entity ent, ref CMExplosiveTriggeredEvent args) + { + DoEffect(ent); + } + + // Sunrise edit start + public void DoEffect(Entity ent) + { + if (!TryComp(ent, out var explosionComponent)) + return; + + if (ent.Comp.ShockWave is { } shockwave) + { + var wave = SpawnNextToOrDrop(shockwave, ent); + ModifyShockwave(wave, explosionComponent); + } + + if (ent.Comp.Explosion is { } explosion) + { + var explosionEntity = SpawnNextToOrDrop(explosion, ent); + CreateFancyExplosionEffect(explosionEntity, explosionComponent); + } + + if (ent.Comp.Smoke is { } smoke) + CreateFancySmoke(ent, explosionComponent, smoke); + } + + private void ModifyShockwave(EntityUid wave, ExplosiveComponent explosionComponent) + { + // Дальше идут просто числа, которые я придумал особо не думая, мб нужно подумать + // Но идея в том, чтобы чем сильнее взрыв, тем сильнее эффект и наоборот + // TODO: Реализовать радиус действия волны и убрать стандартные значения в компоненте + + if (TryComp(wave, out var waveComponent)) + { + waveComponent.FalloffPower ??= explosionComponent.TotalIntensity / 4f; + waveComponent.Width ??= Math.Clamp(explosionComponent.TotalIntensity / 200f, 0.1f, 0.5f); + + Dirty(wave, waveComponent); + } + + if (TryComp(wave, out var timedDespawnComponent)) + timedDespawnComponent.Lifetime = Math.Clamp(explosionComponent.TotalIntensity / 50f, 0.1f, 0.8f); + } + + private void CreateFancyExplosionEffect(EntityUid explosionEntity, ExplosiveComponent explosionComponent) + { + if (!TryComp(explosionEntity, out var timedDespawnComponent)) + return; + + if (!TryComp(explosionEntity, out var explosionEffectComponent)) + return; + + var sizeModifier = Math.Clamp(explosionComponent.TotalIntensity / 50f, 1f, 12f); + explosionEffectComponent.SizeModifier = sizeModifier; + explosionEffectComponent.LifeTime = timedDespawnComponent.Lifetime; + + Dirty(explosionEntity, explosionEffectComponent); + } + + private void CreateFancySmoke(Entity ent, ExplosiveComponent explosionComponent, EntProtoId smokeId) + { + var modifier = explosionComponent.TotalIntensity / 100f; + + var smokeCount = _random.NextFloat(MinSmokeCountPer100 * modifier, MaxSmokeCountPer100 * modifier); + var coords = Transform(ent).Coordinates; + var modifiedRadius = Math.Clamp(SmokeSpawnRadiusPer100 * modifier, 2f, 10f); + + for (var i = 0; i < smokeCount; i++) + { + var smoke = Spawn(smokeId, coords.GetRandomInRadius(modifiedRadius)); + + if (!TryComp(smoke, out var timedDespawnComponent)) + continue; + + if (!TryComp(smoke, out var explosionSmokeEffectComponent)) + continue; + + timedDespawnComponent.Lifetime = ExplosionSmokeEffectComponent.AnimationDuration + _random.NextFloat(-ExplosionSmokeEffectComponent.Variation, ExplosionSmokeEffectComponent.Variation); + + // Это нужно, чтобы анимация на клиенте знала, когда мы решили заканчиваться + explosionSmokeEffectComponent.LifeTime = timedDespawnComponent.Lifetime; + Dirty(smoke, explosionSmokeEffectComponent); + } + } + // Sunrise edit end + + public void TryDoEffect(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return; + + DoEffect((ent, ent.Comp)); + } +} + +[ByRefEvent] +public readonly record struct CMExplosiveTriggeredEvent; + diff --git a/Content.Shared/_RMC14/Xenonids/Screech/RMCXenoScreechShockWaveComponent.cs b/Content.Shared/_RMC14/Xenonids/Screech/RMCXenoScreechShockWaveComponent.cs new file mode 100644 index 0000000000..f78caa9d12 --- /dev/null +++ b/Content.Shared/_RMC14/Xenonids/Screech/RMCXenoScreechShockWaveComponent.cs @@ -0,0 +1,27 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared._RMC14.Xenonids.Screech; + +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] +public sealed partial class RMCXenoScreechShockWaveComponent : Component +{ + /// + /// The speed of each individual wave from the center axis. + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float WaveSpeed = 15.3f; + + /// + /// The size of each wave in its width and distortion effect + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float WaveStrength = 1.0f; + + /// + /// The scale of the effect, lower number means a larger total area while smaller numbers downscale it and reduce the effected area. + /// + [DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)] + public float DownScale = 1f; +} + diff --git a/Content.Shared/_RMC14/Xenonids/Screech/XenoScreechComponent.cs b/Content.Shared/_RMC14/Xenonids/Screech/XenoScreechComponent.cs new file mode 100644 index 0000000000..08905ed73c --- /dev/null +++ b/Content.Shared/_RMC14/Xenonids/Screech/XenoScreechComponent.cs @@ -0,0 +1,15 @@ +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared._RMC14.Xenonids.Screech; + +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class XenoScreechComponent : Component +{ + [DataField, AutoNetworkedField] + public EntProtoId Effect = "CMEffectScreechShort"; + + [DataField, AutoNetworkedField] + public SoundSpecifier Sound = new SoundPathSpecifier("/Audio/_RMC14/Xeno/alien_queen_screech.ogg", AudioParams.Default.WithVolume(-7).WithPlayOffset(1.4f)); +} diff --git a/Content.Shared/_Sunrise/Explosion/SharedSunriseExplosionSystem.cs b/Content.Shared/_Sunrise/Explosion/SharedSunriseExplosionSystem.cs new file mode 100644 index 0000000000..8a79e68a07 --- /dev/null +++ b/Content.Shared/_Sunrise/Explosion/SharedSunriseExplosionSystem.cs @@ -0,0 +1,35 @@ +using Content.Shared._RMC14.Explosion; +using Content.Shared.Explosion; +using Content.Shared.Explosion.Components; +using Robust.Shared.Prototypes; + +namespace Content.Shared._Sunrise.Explosion; + +public sealed class SharedSunriseExplosionSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _prototype = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + } + + private void OnInit(Entity ent, ref ComponentInit args) + { + TryAddExplosionEffect(ent, ent.Comp.ExplosionType); + } + + public bool TryAddExplosionEffect(EntityUid uid, string explosionType) + { + if (!_prototype.TryIndex(explosionType, out var explosionPrototype)) + return false; + + if (explosionPrototype.EffectType != ExplosionEffectType.Fancy) + return false; + + EnsureComp(uid); + return true; + } +} diff --git a/Content.Shared/_Sunrise/Helpers/CoordinatesHelpers.cs b/Content.Shared/_Sunrise/Helpers/CoordinatesHelpers.cs new file mode 100644 index 0000000000..fe97f3d28e --- /dev/null +++ b/Content.Shared/_Sunrise/Helpers/CoordinatesHelpers.cs @@ -0,0 +1,39 @@ +using System.Numerics; +using Robust.Shared.Map; +using Robust.Shared.Random; + +namespace Content.Shared._Sunrise.Helpers; + +public static class EntityCoordinatesExtensions +{ + /// + /// Генерирует случайные координаты в заданном радиусе от исходных координат. + /// + /// Исходные координаты. + /// Радиус для генерации случайной точки. + /// Опциональный экземпляр Random для контроля генерации. + /// Новые координаты в пределах радиуса. + public static EntityCoordinates GetRandomInRadius(this EntityCoordinates origin, float radius, IRobustRandom? rand = null) + { + if (radius < 0) + throw new ArgumentOutOfRangeException(nameof(radius), "Radius cannot be negative."); + + if (radius == 0) + return origin; + + rand ??= IoCManager.Resolve(); + + // Генерируем угол и расстояние. + var angle = rand.NextDouble() * Math.Tau; // 0..2π + var distance = Math.Sqrt(rand.NextDouble()) * radius; // Равномерное распределение + + // Преобразуем в декартовы координаты. + var x = (float)(distance * Math.Cos(angle)); + var y = (float)(distance * Math.Sin(angle)); + + // Смещаем относительно исходной позиции. + var newPosition = origin.Position + new Vector2(x, y); + + return new EntityCoordinates(origin.EntityId, newPosition); + } +} diff --git a/Resources/Audio/_RMC14/Xeno/alien_queen_screech.ogg b/Resources/Audio/_RMC14/Xeno/alien_queen_screech.ogg new file mode 100644 index 0000000000..1fb9ccd531 Binary files /dev/null and b/Resources/Audio/_RMC14/Xeno/alien_queen_screech.ogg differ diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml index 5fb6829ac2..b60fad9773 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Bombs/pipebomb.yml @@ -59,4 +59,4 @@ - type: Construction graph: PipeBomb node: cable - defaultTarget: pipebomb \ No newline at end of file + defaultTarget: pipebomb diff --git a/Resources/Prototypes/Entities/Structures/Machines/nuke.yml b/Resources/Prototypes/Entities/Structures/Machines/nuke.yml index 08cf8dd300..ce7ebc5cb2 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/nuke.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/nuke.yml @@ -92,7 +92,7 @@ enabled: false - type: NukeLabel - type: Nuke - explosionType: Default + explosionType: DefaultStandardEffect # Sunrise edit maxIntensity: 100 intensitySlope: 5 totalIntensity: 5000000 diff --git a/Resources/Prototypes/_RMC14/Effects/explosive.yml b/Resources/Prototypes/_RMC14/Effects/explosive.yml new file mode 100644 index 0000000000..45c6292916 --- /dev/null +++ b/Resources/Prototypes/_RMC14/Effects/explosive.yml @@ -0,0 +1,16 @@ +- type: entity + id: CMExplosionEffectGrenade + components: + - type: Sprite + sprite: _RMC14/Effects/grenade_explosion.rsi + state: grenade + - type: ExplosionEffect # Sunrise + - type: TimedDespawn + lifetime: 0.6 + +- type: entity + id: RMCExplosionEffectGrenadeShockWave + components: + - type: TimedDespawn + lifetime: 0.5 + - type: RMCExplosionShockWave diff --git a/Resources/Prototypes/_RMC14/Effects/screech.yml b/Resources/Prototypes/_RMC14/Effects/screech.yml new file mode 100644 index 0000000000..271ad70b89 --- /dev/null +++ b/Resources/Prototypes/_RMC14/Effects/screech.yml @@ -0,0 +1,35 @@ +- type: entity + # Just fades out with no movement animation + id: CMEffectScreech + categories: [ HideSpawnMenu ] + components: + - type: TimedDespawn + lifetime: 3.2 + - type: RMCXenoScreechShockWave + - type: Sprite + sprite: _RMC14/Effects/xeno_screech.rsi + noRot: true + state: screech + drawdepth: Effects + - type: EffectVisuals + - type: Tag + tags: + - HideContextMenu + +- type: entity + # Just fades out with no movement animation + id: CMEffectScreechShort + categories: [ HideSpawnMenu ] + components: + - type: TimedDespawn + lifetime: 1.4 + - type: RMCXenoScreechShockWave + - type: Sprite + sprite: _RMC14/Effects/xeno_screech.rsi + noRot: true + state: screech + drawdepth: Effects + - type: EffectVisuals + - type: Tag + tags: + - HideContextMenu diff --git a/Resources/Prototypes/_RMC14/Shaders/shaders.yml b/Resources/Prototypes/_RMC14/Shaders/shaders.yml new file mode 100644 index 0000000000..c2a368e1e7 --- /dev/null +++ b/Resources/Prototypes/_RMC14/Shaders/shaders.yml @@ -0,0 +1,9 @@ +- type: shader + id: RMCShockWave + kind: source + path: "/Textures/_RMC14/Shaders/shock_wave.swsl" + +- type: shader + id: RMCXenoScreechShockWave + kind: source + path: "/Textures/_RMC14/Shaders/screech_shock_wave.swsl" \ No newline at end of file diff --git a/Resources/Prototypes/_Sunrise/Entities/Effects/explosion.yml b/Resources/Prototypes/_Sunrise/Entities/Effects/explosion.yml new file mode 100644 index 0000000000..e509a4eb59 --- /dev/null +++ b/Resources/Prototypes/_Sunrise/Entities/Effects/explosion.yml @@ -0,0 +1,11 @@ +- type: entity + id: ExplosionEffectSmoke + components: + - type: Sprite + drawdepth: Effects + sprite: _Sunrise/Effects/explosion_smoke.rsi + state: smoke + shader: unshaded + - type: ExplosionSmokeEffect + - type: TimedDespawn + lifetime: 5 diff --git a/Resources/Prototypes/_Sunrise/explosion.yml b/Resources/Prototypes/_Sunrise/explosion.yml new file mode 100644 index 0000000000..a826a298a7 --- /dev/null +++ b/Resources/Prototypes/_Sunrise/explosion.yml @@ -0,0 +1,14 @@ +- type: explosion + id: DefaultStandardEffect + damagePerIntensity: + types: + Heat: 5 + Blunt: 5 + Piercing: 5 + tileBreakChance: [0, 0.5, 1] + tileBreakIntensity: [0, 10, 30] + tileBreakRerollReduction: 20 + lightColor: Orange + texturePath: /Textures/Effects/fire.rsi + fireStates: 3 + effectType: Standard diff --git a/Resources/Textures/_RMC14/Effects/grenade_explosion.rsi/grenade.png b/Resources/Textures/_RMC14/Effects/grenade_explosion.rsi/grenade.png new file mode 100644 index 0000000000..d00b7283af Binary files /dev/null and b/Resources/Textures/_RMC14/Effects/grenade_explosion.rsi/grenade.png differ diff --git a/Resources/Textures/_RMC14/Effects/grenade_explosion.rsi/meta.json b/Resources/Textures/_RMC14/Effects/grenade_explosion.rsi/meta.json new file mode 100644 index 0000000000..faef7a9557 --- /dev/null +++ b/Resources/Textures/_RMC14/Effects/grenade_explosion.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/add6123ac6b3263f257e4b233ef5a8fea5d3d317/icons/effects/explosion.dmi", + "size": { + "x": 48, + "y": 48 + }, + "states": [ + { + "name": "grenade", + "delays": [ + [ + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1, + 0.1 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_RMC14/Effects/xeno_screech.rsi/meta.json b/Resources/Textures/_RMC14/Effects/xeno_screech.rsi/meta.json new file mode 100644 index 0000000000..0d9e8d5bad --- /dev/null +++ b/Resources/Textures/_RMC14/Effects/xeno_screech.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/d5b119380250ea512db2a5319e36592c7f604250/icons/effects/xeno_screech.dmi", + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "screech", + "delays": [ + [ + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2, + 0.2 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_RMC14/Effects/xeno_screech.rsi/screech.png b/Resources/Textures/_RMC14/Effects/xeno_screech.rsi/screech.png new file mode 100644 index 0000000000..e93bed0eab Binary files /dev/null and b/Resources/Textures/_RMC14/Effects/xeno_screech.rsi/screech.png differ diff --git a/Resources/Textures/_RMC14/Shaders/screech_shock_wave.swsl b/Resources/Textures/_RMC14/Shaders/screech_shock_wave.swsl new file mode 100644 index 0000000000..28eeb23762 --- /dev/null +++ b/Resources/Textures/_RMC14/Shaders/screech_shock_wave.swsl @@ -0,0 +1,28 @@ +uniform sampler2D SCREEN_TEXTURE; +uniform highp float waveStrength; +uniform highp vec2 position; +uniform highp float waveSpeed; +uniform highp float downScale; + +void fragment() +{ + highp vec2 st = UV; + highp vec2 WaveCentre = position; + highp float ratio = SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x * 0.5; + WaveCentre.y *= ratio; + highp float dist = distance(vec2(st.x, st.y * ratio), WaveCentre) * downScale; + highp float val = dist; + highp float a = 3.0; + highp float cosFuns = cos(val * 20.0 - TIME * waveSpeed); + highp float powFuns = pow(val * 2.5, a); + highp float limtedPowFuns = 0.5 * pow(a / (a + powFuns), 2.0); + highp float finalRes = smoothstep(0.0, 1.0, limtedPowFuns * cosFuns) * waveStrength; + highp vec3 col = finalRes * vec3(1.0); + st = st * 2.0 - 1.0; + st *= 1.0 + finalRes * 0.1; + st = st * 0.5 + 0.5; + highp vec4 texCol = zTextureSpec(SCREEN_TEXTURE, st); + texCol += (texCol * finalRes) / (dist * 10.0); + + COLOR = texCol; +} diff --git a/Resources/Textures/_RMC14/Shaders/shock_wave.swsl b/Resources/Textures/_RMC14/Shaders/shock_wave.swsl new file mode 100644 index 0000000000..6e2ce1bc26 --- /dev/null +++ b/Resources/Textures/_RMC14/Shaders/shock_wave.swsl @@ -0,0 +1,40 @@ +uniform sampler2D SCREEN_TEXTURE; +uniform highp vec2 renderScale; +uniform lowp int count; +uniform highp vec2[10] position; +uniform highp float[10] sharpness; +uniform highp float[10] width; +uniform highp float[10] falloffPower; +uniform highp float[10] time; + +void fragment() { + highp vec2 coord = FRAGCOORD.xy * SCREEN_PIXEL_SIZE.xy; + highp float ratio = SCREEN_PIXEL_SIZE.y / SCREEN_PIXEL_SIZE.x * 0.6; + + highp vec2 totalOffset = vec2(0.0); + + for (int i = 0; i < count; ++i) { + highp vec2 WaveCentre = position[i]; + + highp float Dist = distance( + vec2(coord.x, coord.y * ratio), + vec2(WaveCentre.x, WaveCentre.y * ratio) + ); + + highp float CurrentTime = time[i]; + + if (Dist <= (CurrentTime + 0.1) && Dist >= (CurrentTime - 0.1)) { + highp float Diff = Dist - CurrentTime; + highp float ScaleDiff = 1.0 - pow(abs(Diff * sharpness[i]), width[i]); + highp float DiffTime = Diff * ScaleDiff; + highp vec2 DiffTexCoord = normalize(coord - WaveCentre); + + totalOffset += (DiffTexCoord * DiffTime) / (CurrentTime * Dist * falloffPower[i]); + } + } + + coord += totalOffset; + + highp vec4 Color = texture(SCREEN_TEXTURE, coord); + COLOR = Color; +} diff --git a/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/dust.png b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/dust.png new file mode 100644 index 0000000000..91101e019f Binary files /dev/null and b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/dust.png differ diff --git a/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/fire.png b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/fire.png new file mode 100644 index 0000000000..b8fd19b042 Binary files /dev/null and b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/fire.png differ diff --git a/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/gas.png b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/gas.png new file mode 100644 index 0000000000..f8cae98ddf Binary files /dev/null and b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/gas.png differ diff --git a/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/meta.json b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/meta.json new file mode 100644 index 0000000000..f569eb2d7c --- /dev/null +++ b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/meta.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "license": "CLA", + "copyright": "SUNRISE", + "size": + { + "x": 128, + "y": 128 + }, + "states": + [ + { + "name": "smoke" + }, + { + "name": "dust" + }, + { + "name": "fire" + }, + { + "name": "gas" + } + ] +} diff --git a/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/smoke.png b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/smoke.png new file mode 100644 index 0000000000..4d8da1a69e Binary files /dev/null and b/Resources/Textures/_Sunrise/Effects/explosion_smoke.rsi/smoke.png differ