Новые эффекты взрывов (#1410)

Co-authored-by: Vero <73014819+vero5123@users.noreply.github.com>
Co-authored-by: Whisper <121047731+QuietlyWhisper@users.noreply.github.com>
Co-authored-by: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com>
This commit is contained in:
ThereDrD 2025-02-20 17:29:41 +03:00 committed by GitHub
parent 26f5302d6a
commit 6c8050eb1e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 910 additions and 9 deletions

View file

@ -1,3 +1,5 @@
using Content.Client._RMC14.Explosion;
using Content.Client._RMC14.Xenonids.Screech;
using Content.Client._Sunrise.Entry; using Content.Client._Sunrise.Entry;
using Content.Client._Sunrise.ServersHub; using Content.Client._Sunrise.ServersHub;
using Content.Client.Administration.Managers; using Content.Client.Administration.Managers;
@ -174,6 +176,10 @@ namespace Content.Client.Entry
_parallaxManager.LoadDefaultParallax(); _parallaxManager.LoadDefaultParallax();
_overlayManager.AddOverlay(new SingularityOverlay()); _overlayManager.AddOverlay(new SingularityOverlay());
// Sunrise edit start
_overlayManager.AddOverlay(new RMCExplosionShockWaveOverlay());
_overlayManager.AddOverlay(new RMCXenoScreechShockWaveOverlay());
// Sunrise edit end
_overlayManager.AddOverlay(new RadiationPulseOverlay()); _overlayManager.AddOverlay(new RadiationPulseOverlay());
_chatManager.Initialize(); _chatManager.Initialize();
_clientPreferencesManager.Initialize(); _clientPreferencesManager.Initialize();

View file

@ -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;
/// <summary>
/// Maximum number of distortions that can be shown on screen at a time.
/// </summary>
public const int MaxCount = 10;
public RMCExplosionShockWaveOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("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<RMCExplosionShockWaveComponent, TransformComponent>();
_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);
}
}

View file

@ -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<ShaderPrototype>("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<RMCXenoScreechShockWaveComponent, TransformComponent>();
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);
}
}

View file

@ -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<RMCExplosionShockWaveComponent, ComponentInit>(OnInit);
}
private void OnInit(Entity<RMCExplosionShockWaveComponent> ent, ref ComponentInit args)
{
ent.Comp.CreationTime = _timing.CurTime;
}
}

View file

@ -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<ExplosionSmokeEffectComponent, ComponentStartup>(OnSmokeStartup);
SubscribeLocalEvent<ExplosionEffectComponent, ComponentStartup>(OnExplosionStartup);
}
private void OnSmokeStartup(Entity<ExplosionSmokeEffectComponent> ent, ref ComponentStartup args)
{
if (!TryComp<SpriteComponent>(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<ExplosionEffectComponent> ent, ref ComponentStartup args)
{
if (!TryComp<SpriteComponent>(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);
}
}

View file

@ -20,6 +20,8 @@ using Content.Shared.Stealth.Components;
using Content.Shared.Damage.Components; using Content.Shared.Damage.Components;
using Content.Server.Radio.Components; using Content.Server.Radio.Components;
using Content.Shared._Sunrise.CollectiveMind; using Content.Shared._Sunrise.CollectiveMind;
using Content.Shared._RMC14.Xenonids.Screech;
using Content.Shared.Coordinates;
namespace Content.Server.Changeling; namespace Content.Server.Changeling;
@ -263,6 +265,10 @@ public sealed partial class ChangelingSystem : EntitySystem
_popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid);
// Sunrise edit start
StartScreech(uid);
// Sunrise edit end
comp.IsInStasis = false; comp.IsInStasis = false;
args.Handled = true; args.Handled = true;
@ -637,4 +643,17 @@ public sealed partial class ChangelingSystem : EntitySystem
} }
#endregion #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
} }

View file

@ -55,6 +55,7 @@ using Content.Shared.Mobs.Components;
using Content.Server.Stunnable; using Content.Server.Stunnable;
using Content.Shared.Jittering; using Content.Shared.Jittering;
using System.Linq; using System.Linq;
using Content.Shared._RMC14.Xenonids.Screech;
using Content.Shared.Forensics.Components; using Content.Shared.Forensics.Components;
using Content.Shared.Radio; using Content.Shared.Radio;
@ -266,6 +267,10 @@ public sealed partial class ChangelingSystem : EntitySystem
{ {
_audio.PlayPvs(comp.ShriekSound, uid); _audio.PlayPvs(comp.ShriekSound, uid);
// Sunrise edit start
StartScreech(uid, playSound: false);
// Sunrise edit end
var center = Transform(uid).MapPosition; var center = Transform(uid).MapPosition;
var gamers = Filter.Empty(); var gamers = Filter.Empty();
gamers.AddInRange(center, comp.ShriekPower, _player, EntityManager); gamers.AddInRange(center, comp.ShriekPower, _player, EntityManager);
@ -607,6 +612,10 @@ public sealed partial class ChangelingSystem : EntitySystem
RemComp<ThirstComponent>(uid); RemComp<ThirstComponent>(uid);
EnsureComp<ZombieImmuneComponent>(uid); EnsureComp<ZombieImmuneComponent>(uid);
// Sunrise edit start
EnsureComp<XenoScreechComponent>(uid);
// Sunrise edit end
// add actions // add actions
foreach (var actionId in comp.BaseChangelingActions) foreach (var actionId in comp.BaseChangelingActions)
_actions.AddAction(uid, actionId); _actions.AddAction(uid, actionId);

View file

@ -5,6 +5,7 @@ using Content.Shared.Explosion;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Content.Shared._Sunrise.Explosion;
namespace Content.Server.EntityEffects.Effects; namespace Content.Server.EntityEffects.Effects;
@ -68,6 +69,11 @@ public sealed partial class ExplosionReactionEffect : EntityEffect
intensity = MathF.Min((float) reagentArgs.Quantity * IntensityPerUnit, MaxTotalIntensity); intensity = MathF.Min((float) reagentArgs.Quantity * IntensityPerUnit, MaxTotalIntensity);
} }
// Sunrise edit start
args.EntityManager.System<SharedSunriseExplosionSystem>()
.TryAddExplosionEffect(args.TargetEntity, ExplosionType);
// Sunrise edit end
args.EntityManager.System<ExplosionSystem>() args.EntityManager.System<ExplosionSystem>()
.QueueExplosion( .QueueExplosion(
args.TargetEntity, args.TargetEntity,

View file

@ -37,9 +37,15 @@ public sealed partial class ExplosionSystem
/// <summary> /// <summary>
/// Constructor for the shared <see cref="ExplosionEvent"/> using the server-exclusive explosion classes. /// Constructor for the shared <see cref="ExplosionEvent"/> using the server-exclusive explosion classes.
/// </summary> /// </summary>
private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, string prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable<ExplosionGridTileFlood> gridData, List<float> iterationIntensity) private EntityUid CreateExplosionVisualEntity(MapCoordinates epicenter, ExplosionPrototype prototype, Matrix3x2 spaceMatrix, ExplosionSpaceTileFlood? spaceData, IEnumerable<ExplosionGridTileFlood> gridData, List<float> iterationIntensity)
{ {
var explosionEntity = Spawn(null, MapCoordinates.Nullspace); var explosionEntity = Spawn(null, MapCoordinates.Nullspace);
// Sunrise added start
if (prototype.EffectType != ExplosionEffectType.Standard)
return explosionEntity;
// Sunrise added end
var comp = AddComp<ExplosionVisualsComponent>(explosionEntity); var comp = AddComp<ExplosionVisualsComponent>(explosionEntity);
foreach (var grid in gridData) foreach (var grid in gridData)
@ -49,7 +55,7 @@ public sealed partial class ExplosionSystem
comp.SpaceTiles = spaceData?.TileLists; comp.SpaceTiles = spaceData?.TileLists;
comp.Epicenter = epicenter; comp.Epicenter = epicenter;
comp.ExplosionType = prototype; comp.ExplosionType = prototype.ID; // Sunrise edit
comp.Intensity = iterationIntensity; comp.Intensity = iterationIntensity;
comp.SpaceMatrix = spaceMatrix; comp.SpaceMatrix = spaceMatrix;
comp.SpaceTileSize = spaceData?.TileSize ?? DefaultTileSize; comp.SpaceTileSize = spaceData?.TileSize ?? DefaultTileSize;

View file

@ -5,6 +5,7 @@ using Content.Server.Atmos.Components;
using Content.Server.Chat.Managers; using Content.Server.Chat.Managers;
using Content.Server.NodeContainer.EntitySystems; using Content.Server.NodeContainer.EntitySystems;
using Content.Server.NPC.Pathfinding; using Content.Server.NPC.Pathfinding;
using Content.Shared._RMC14.Explosion;
using Content.Shared.Camera; using Content.Shared.Camera;
using Content.Shared.CCVar; using Content.Shared.CCVar;
using Content.Shared.Damage; using Content.Shared.Damage;
@ -319,6 +320,14 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
}; };
_explosionQueue.Enqueue(boom); _explosionQueue.Enqueue(boom);
_queuedExplosions.Add(boom); _queuedExplosions.Add(boom);
// Sunrise added start
if (!cause.HasValue)
return;
var ev = new CMExplosiveTriggeredEvent();
RaiseLocalEvent(cause.Value, ref ev);
// Sunrise added end
} }
/// <summary> /// <summary>
@ -339,7 +348,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
var (area, iterationIntensity, spaceData, gridData, spaceMatrix) = results.Value; 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 // camera shake
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity); CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);

View file

@ -0,0 +1,8 @@
using Content.Shared._RMC14.Explosion;
namespace Content.Server._Sunrise.Explosions;
public sealed class RMCExplosionSystem : SharedRMCExplosionSystem
{
}

View file

@ -1,6 +1,7 @@
using Content.Shared.Damage; using Content.Shared.Damage;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility; using Robust.Shared.Utility;
namespace Content.Shared.Explosion; namespace Content.Shared.Explosion;
@ -110,6 +111,11 @@ public sealed partial class ExplosionPrototype : IPrototype
[DataField("fireStates")] [DataField("fireStates")]
public int FireStates = 3; public int FireStates = 3;
// Sunrise added start
[DataField]
public ExplosionEffectType EffectType = ExplosionEffectType.Fancy;
// Sunrise added end
/// <summary> /// <summary>
/// Basic function for linear interpolation of the _tileBreakChance and _tileBreakIntensity arrays /// Basic function for linear interpolation of the _tileBreakChance and _tileBreakIntensity arrays
/// </summary> /// </summary>
@ -133,3 +139,11 @@ public sealed partial class ExplosionPrototype : IPrototype
return _tileBreakChance[i - 1] + slope * (intensity - _tileBreakIntensity[i - 1]); return _tileBreakChance[i - 1] + slope * (intensity - _tileBreakIntensity[i - 1]);
} }
} }
[Serializable, NetSerializable]
public enum ExplosionEffectType : byte
{
Standard,
Fancy,
}

View file

@ -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; return;
component.Shooter = shooterId; component.Shooter = shooterId;

View file

@ -484,7 +484,7 @@ public abstract partial class SharedGunSystem : EntitySystem
Physics.SetLinearVelocity(uid, finalLinear, body: physics); Physics.SetLinearVelocity(uid, finalLinear, body: physics);
var projectile = EnsureComp<ProjectileComponent>(uid); var projectile = EnsureComp<ProjectileComponent>(uid);
Projectiles.SetShooter(uid, projectile, user ?? gunUid); Projectiles.SetShooter(uid, projectile, user);
projectile.Weapon = gunUid; projectile.Weapon = gunUid;
// Sunrise-Start // Sunrise-Start

View file

@ -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

View file

@ -0,0 +1,30 @@
using Robust.Shared.GameStates;
namespace Content.Shared._RMC14.Explosion.Components
{
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
public sealed partial class RMCExplosionShockWaveComponent : Component
{
/// <summary>
/// The rate at which the wave fades, lower values means it's active for longer.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public float? FalloffPower = 20f;
/// <summary>
/// How sharp the wave distortion is. Higher values make the wave more pronounced.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public float Sharpness = 5.0f;
/// <summary>
/// Width of the wave.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public float? Width = 0.8f;
[DataField]
public TimeSpan CreationTime;
}
}

View file

@ -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<CMExplosionEffectComponent, CMExplosiveTriggeredEvent>(OnExplosionEffectTriggered);
}
private void OnExplosionEffectTriggered(Entity<CMExplosionEffectComponent> ent, ref CMExplosiveTriggeredEvent args)
{
DoEffect(ent);
}
// Sunrise edit start
public void DoEffect(Entity<CMExplosionEffectComponent> ent)
{
if (!TryComp<ExplosiveComponent>(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<RMCExplosionShockWaveComponent>(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<TimedDespawnComponent>(wave, out var timedDespawnComponent))
timedDespawnComponent.Lifetime = Math.Clamp(explosionComponent.TotalIntensity / 50f, 0.1f, 0.8f);
}
private void CreateFancyExplosionEffect(EntityUid explosionEntity, ExplosiveComponent explosionComponent)
{
if (!TryComp<TimedDespawnComponent>(explosionEntity, out var timedDespawnComponent))
return;
if (!TryComp<ExplosionEffectComponent>(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<CMExplosionEffectComponent> 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<TimedDespawnComponent>(smoke, out var timedDespawnComponent))
continue;
if (!TryComp<ExplosionSmokeEffectComponent>(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<CMExplosionEffectComponent?> ent)
{
if (!Resolve(ent, ref ent.Comp, false))
return;
DoEffect((ent, ent.Comp));
}
}
[ByRefEvent]
public readonly record struct CMExplosiveTriggeredEvent;

View file

@ -0,0 +1,27 @@
using Robust.Shared.GameStates;
namespace Content.Shared._RMC14.Xenonids.Screech;
[RegisterComponent, NetworkedComponent]
[AutoGenerateComponentState]
public sealed partial class RMCXenoScreechShockWaveComponent : Component
{
/// <summary>
/// The speed of each individual wave from the center axis.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public float WaveSpeed = 15.3f;
/// <summary>
/// The size of each wave in its width and distortion effect
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public float WaveStrength = 1.0f;
/// <summary>
/// The scale of the effect, lower number means a larger total area while smaller numbers downscale it and reduce the effected area.
/// </summary>
[DataField, AutoNetworkedField, ViewVariables(VVAccess.ReadWrite)]
public float DownScale = 1f;
}

View file

@ -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));
}

View file

@ -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<ExplosiveComponent, ComponentInit>(OnInit);
}
private void OnInit(Entity<ExplosiveComponent> ent, ref ComponentInit args)
{
TryAddExplosionEffect(ent, ent.Comp.ExplosionType);
}
public bool TryAddExplosionEffect(EntityUid uid, string explosionType)
{
if (!_prototype.TryIndex<ExplosionPrototype>(explosionType, out var explosionPrototype))
return false;
if (explosionPrototype.EffectType != ExplosionEffectType.Fancy)
return false;
EnsureComp<CMExplosionEffectComponent>(uid);
return true;
}
}

View file

@ -0,0 +1,39 @@
using System.Numerics;
using Robust.Shared.Map;
using Robust.Shared.Random;
namespace Content.Shared._Sunrise.Helpers;
public static class EntityCoordinatesExtensions
{
/// <summary>
/// Генерирует случайные координаты в заданном радиусе от исходных координат.
/// </summary>
/// <param name="origin">Исходные координаты.</param>
/// <param name="radius">Радиус для генерации случайной точки.</param>
/// <param name="rand">Опциональный экземпляр Random для контроля генерации.</param>
/// <returns>Новые координаты в пределах радиуса.</returns>
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<IRobustRandom>();
// Генерируем угол и расстояние.
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);
}
}

Binary file not shown.

View file

@ -92,7 +92,7 @@
enabled: false enabled: false
- type: NukeLabel - type: NukeLabel
- type: Nuke - type: Nuke
explosionType: Default explosionType: DefaultStandardEffect # Sunrise edit
maxIntensity: 100 maxIntensity: 100
intensitySlope: 5 intensitySlope: 5
totalIntensity: 5000000 totalIntensity: 5000000

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View file

@ -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
]
]
}
]
}

View file

@ -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
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -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;
}

View file

@ -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;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,25 @@
{
"version": 1,
"license": "CLA",
"copyright": "SUNRISE",
"size":
{
"x": 128,
"y": 128
},
"states":
[
{
"name": "smoke"
},
{
"name": "dust"
},
{
"name": "fire"
},
{
"name": "gas"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB