combine same-tile explosions in the same tick (#25664)
* combine same-tile explosions in the same tick * ! --------- Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
@@ -38,8 +38,15 @@ public sealed partial class ExplosionSystem
|
|||||||
/// Queue for delayed processing of explosions. If there is an explosion that covers more than <see
|
/// Queue for delayed processing of explosions. If there is an explosion that covers more than <see
|
||||||
/// cref="TilesPerTick"/> tiles, other explosions will actually be delayed slightly. Unless it's a station
|
/// cref="TilesPerTick"/> tiles, other explosions will actually be delayed slightly. Unless it's a station
|
||||||
/// nuke, this delay should never really be noticeable.
|
/// nuke, this delay should never really be noticeable.
|
||||||
|
/// This is also used to combine explosion intensities of the same kind.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Queue<Func<Explosion?>> _explosionQueue = new();
|
private Queue<QueuedExplosion> _explosionQueue = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All queued explosions that will be processed in <see cref="_explosionQueue"/>.
|
||||||
|
/// These always have the same contents.
|
||||||
|
/// </summary>
|
||||||
|
private HashSet<QueuedExplosion> _queuedExplosions = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The explosion currently being processed.
|
/// The explosion currently being processed.
|
||||||
@@ -93,10 +100,11 @@ public sealed partial class ExplosionSystem
|
|||||||
if (MathF.Max(MaxProcessingTime - 1, 0.1f) < Stopwatch.Elapsed.TotalMilliseconds)
|
if (MathF.Max(MaxProcessingTime - 1, 0.1f) < Stopwatch.Elapsed.TotalMilliseconds)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (!_explosionQueue.TryDequeue(out var spawnNextExplosion))
|
if (!_explosionQueue.TryDequeue(out var queued))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
_activeExplosion = spawnNextExplosion();
|
_queuedExplosions.Remove(queued);
|
||||||
|
_activeExplosion = SpawnExplosion(queued);
|
||||||
|
|
||||||
// explosion spawning can be null if something somewhere went wrong. (e.g., negative explosion
|
// explosion spawning can be null if something somewhere went wrong. (e.g., negative explosion
|
||||||
// intensity).
|
// intensity).
|
||||||
@@ -867,3 +875,15 @@ sealed class Explosion
|
|||||||
_tileUpdateDict.Clear();
|
_tileUpdateDict.Clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Data needed to spawn an explosion with <see cref="ExplosionSystem.SpawnExplosion"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class QueuedExplosion
|
||||||
|
{
|
||||||
|
public MapCoordinates Epicenter;
|
||||||
|
public ExplosionPrototype Proto = new();
|
||||||
|
public float TotalIntensity, Slope, MaxTileIntensity, TileBreakScale;
|
||||||
|
public int MaxTileBreak;
|
||||||
|
public bool CanCreateVacuum;
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ public sealed partial class ExplosionSystem : EntitySystem
|
|||||||
private void OnReset(RoundRestartCleanupEvent ev)
|
private void OnReset(RoundRestartCleanupEvent ev)
|
||||||
{
|
{
|
||||||
_explosionQueue.Clear();
|
_explosionQueue.Clear();
|
||||||
|
_queuedExplosions.Clear();
|
||||||
if (_activeExplosion != null)
|
if (_activeExplosion != null)
|
||||||
QueueDel(_activeExplosion.VisualEnt);
|
QueueDel(_activeExplosion.VisualEnt);
|
||||||
_activeExplosion = null;
|
_activeExplosion = null;
|
||||||
@@ -297,8 +298,36 @@ public sealed partial class ExplosionSystem : EntitySystem
|
|||||||
if (addLog) // dont log if already created a separate, more detailed, log.
|
if (addLog) // dont log if already created a separate, more detailed, log.
|
||||||
_adminLogger.Add(LogType.Explosion, LogImpact.High, $"Explosion ({typeId}) spawned at {epicenter:coordinates} with intensity {totalIntensity} slope {slope}");
|
_adminLogger.Add(LogType.Explosion, LogImpact.High, $"Explosion ({typeId}) spawned at {epicenter:coordinates} with intensity {totalIntensity} slope {slope}");
|
||||||
|
|
||||||
_explosionQueue.Enqueue(() => SpawnExplosion(epicenter, type, totalIntensity,
|
// try to combine explosions on the same tile if they are the same type
|
||||||
slope, maxTileIntensity, tileBreakScale, maxTileBreak, canCreateVacuum));
|
foreach (var queued in _queuedExplosions)
|
||||||
|
{
|
||||||
|
// ignore different types or those on different maps
|
||||||
|
if (queued.Proto.ID != type.ID || queued.Epicenter.MapId != epicenter.MapId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var dst2 = queued.Proto.MaxCombineDistance * queued.Proto.MaxCombineDistance;
|
||||||
|
var direction = queued.Epicenter.Position - epicenter.Position;
|
||||||
|
if (direction.LengthSquared() > dst2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// they are close enough to combine so just add total intensity and prevent queuing another one
|
||||||
|
queued.TotalIntensity += totalIntensity;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var boom = new QueuedExplosion()
|
||||||
|
{
|
||||||
|
Epicenter = epicenter,
|
||||||
|
Proto = type,
|
||||||
|
TotalIntensity = totalIntensity,
|
||||||
|
Slope = slope,
|
||||||
|
MaxTileIntensity = maxTileIntensity,
|
||||||
|
TileBreakScale = tileBreakScale,
|
||||||
|
MaxTileBreak = maxTileBreak,
|
||||||
|
CanCreateVacuum = canCreateVacuum
|
||||||
|
};
|
||||||
|
_explosionQueue.Enqueue(boom);
|
||||||
|
_queuedExplosions.Add(boom);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -306,32 +335,26 @@ public sealed partial class ExplosionSystem : EntitySystem
|
|||||||
/// information about the affected tiles for the explosion system to process. It will also trigger the
|
/// information about the affected tiles for the explosion system to process. It will also trigger the
|
||||||
/// camera shake and sound effect.
|
/// camera shake and sound effect.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private Explosion? SpawnExplosion(MapCoordinates epicenter,
|
private Explosion? SpawnExplosion(QueuedExplosion queued)
|
||||||
ExplosionPrototype type,
|
|
||||||
float totalIntensity,
|
|
||||||
float slope,
|
|
||||||
float maxTileIntensity,
|
|
||||||
float tileBreakScale,
|
|
||||||
int maxTileBreak,
|
|
||||||
bool canCreateVacuum)
|
|
||||||
{
|
{
|
||||||
if (!_mapManager.MapExists(epicenter.MapId))
|
var pos = queued.Epicenter;
|
||||||
|
if (!_mapManager.MapExists(pos.MapId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var results = GetExplosionTiles(epicenter, type.ID, totalIntensity, slope, maxTileIntensity);
|
var results = GetExplosionTiles(pos, queued.Proto.ID, queued.TotalIntensity, queued.Slope, queued.MaxTileIntensity);
|
||||||
|
|
||||||
if (results == null)
|
if (results == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var (area, iterationIntensity, spaceData, gridData, spaceMatrix) = results.Value;
|
var (area, iterationIntensity, spaceData, gridData, spaceMatrix) = results.Value;
|
||||||
|
|
||||||
var visualEnt = CreateExplosionVisualEntity(epicenter, type.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
var visualEnt = CreateExplosionVisualEntity(pos, queued.Proto.ID, spaceMatrix, spaceData, gridData.Values, iterationIntensity);
|
||||||
|
|
||||||
// camera shake
|
// camera shake
|
||||||
CameraShake(iterationIntensity.Count * 4f, epicenter, totalIntensity);
|
CameraShake(iterationIntensity.Count * 4f, pos, queued.TotalIntensity);
|
||||||
|
|
||||||
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
//For whatever bloody reason, sound system requires ENTITY coordinates.
|
||||||
var mapEntityCoords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(epicenter.MapId), epicenter, _transformSystem, EntityManager);
|
var mapEntityCoords = EntityCoordinates.FromMap(_mapManager.GetMapEntityId(pos.MapId), pos, _transformSystem, EntityManager);
|
||||||
|
|
||||||
// play sound.
|
// play sound.
|
||||||
// for the normal audio, we want everyone in pvs range
|
// for the normal audio, we want everyone in pvs range
|
||||||
@@ -339,34 +362,35 @@ public sealed partial class ExplosionSystem : EntitySystem
|
|||||||
// this is capped to 30 because otherwise really huge bombs
|
// this is capped to 30 because otherwise really huge bombs
|
||||||
// will attempt to play regular audio for people who can't hear it anyway because the epicenter is so far away
|
// will attempt to play regular audio for people who can't hear it anyway because the epicenter is so far away
|
||||||
var audioRange = Math.Min(iterationIntensity.Count * 2, MaxExplosionAudioRange);
|
var audioRange = Math.Min(iterationIntensity.Count * 2, MaxExplosionAudioRange);
|
||||||
var filter = Filter.Pvs(epicenter).AddInRange(epicenter, audioRange);
|
var filter = Filter.Pvs(pos).AddInRange(pos, audioRange);
|
||||||
var sound = iterationIntensity.Count < type.SmallSoundIterationThreshold
|
var sound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||||
? type.SmallSound
|
? queued.Proto.SmallSound
|
||||||
: type.Sound;
|
: queued.Proto.Sound;
|
||||||
|
|
||||||
_audio.PlayStatic(sound, filter, mapEntityCoords, true, sound.Params);
|
_audio.PlayStatic(sound, filter, mapEntityCoords, true, sound.Params);
|
||||||
|
|
||||||
// play far sound
|
// play far sound
|
||||||
// far sound should play for anyone who wasn't in range of any of the effects of the bomb
|
// far sound should play for anyone who wasn't in range of any of the effects of the bomb
|
||||||
var farAudioRange = iterationIntensity.Count * 5;
|
var farAudioRange = iterationIntensity.Count * 5;
|
||||||
var farFilter = Filter.Empty().AddInRange(epicenter, farAudioRange).RemoveInRange(epicenter, audioRange);
|
var farFilter = Filter.Empty().AddInRange(pos, farAudioRange).RemoveInRange(pos, audioRange);
|
||||||
var farSound = iterationIntensity.Count < type.SmallSoundIterationThreshold
|
var farSound = iterationIntensity.Count < queued.Proto.SmallSoundIterationThreshold
|
||||||
? type.SmallSoundFar
|
? queued.Proto.SmallSoundFar
|
||||||
: type.SoundFar;
|
: queued.Proto.SoundFar;
|
||||||
|
|
||||||
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
_audio.PlayGlobal(farSound, farFilter, true, farSound.Params);
|
||||||
|
|
||||||
return new Explosion(this,
|
return new Explosion(this,
|
||||||
type,
|
queued.Proto,
|
||||||
spaceData,
|
spaceData,
|
||||||
gridData.Values.ToList(),
|
gridData.Values.ToList(),
|
||||||
iterationIntensity,
|
iterationIntensity,
|
||||||
epicenter,
|
pos,
|
||||||
spaceMatrix,
|
spaceMatrix,
|
||||||
area,
|
area,
|
||||||
tileBreakScale,
|
// TODO: instead of le copy paste fields refactor so it has QueuedExplosion as a field?
|
||||||
maxTileBreak,
|
queued.TileBreakScale,
|
||||||
canCreateVacuum,
|
queued.MaxTileBreak,
|
||||||
|
queued.CanCreateVacuum,
|
||||||
EntityManager,
|
EntityManager,
|
||||||
_mapManager,
|
_mapManager,
|
||||||
visualEnt);
|
visualEnt);
|
||||||
|
|||||||
@@ -77,6 +77,13 @@ public sealed partial class ExplosionPrototype : IPrototype
|
|||||||
[DataField("smallSoundIterationThreshold")]
|
[DataField("smallSoundIterationThreshold")]
|
||||||
public int SmallSoundIterationThreshold = 6;
|
public int SmallSoundIterationThreshold = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How far away another explosion in the same tick can be and be combined.
|
||||||
|
/// Total intensity is added to the original queued explosion.
|
||||||
|
/// </summary>
|
||||||
|
[DataField]
|
||||||
|
public float MaxCombineDistance = 1f;
|
||||||
|
|
||||||
[DataField("sound")]
|
[DataField("sound")]
|
||||||
public SoundSpecifier Sound = new SoundCollectionSpecifier("Explosion");
|
public SoundSpecifier Sound = new SoundCollectionSpecifier("Explosion");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user