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:
deltanedas
2024-03-29 23:46:05 +00:00
committed by GitHub
parent 28d05feea7
commit 355d00a0f2
3 changed files with 82 additions and 31 deletions

View File

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

View File

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

View File

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