Tile Stacking - attempt 2 (#42543)

* WORK IN PROGRESS 1

* ITS ALIVE, ALIVE!!!!

* clean up

* WIP 1

* fix small oversight

* big diff of doom

* added CVAR to tile history stack size

* component time

* filescoped namespaces + remove redundant nametag

* fix silly little mistakes

* typo

* TileStacksTest

* bweeeeeeh :P

* nuke cvar

* :3

* WIP2025

* Fix submodule

* It's beginning to look a lot like Christmas

* It's the Most Wonderful Time of the Year

* tiny fix

* fixed extra spacing on yaml

* slightly improve tilestacking test

* Part 1 out of 2 (part 2 tomorrow)

* Part 2

* add a simple tile construction test for tilestacking

* guh

* address reviews (no documentation yet)

* documentation be upon ye

* remove extra spaces

* prediction fix

* dirt

* oops :p

* attempt to make this suck less

* am never doing a PR that touches explosion system ever again

* small fix

* maybe a bit better now?

* more cleanup

* one last cleanup for me

---------

Co-authored-by: Killerqu00 <killerqueen1777@gmail.com>
Co-authored-by: Killerqu00 <47712032+Killerqu00@users.noreply.github.com>
Co-authored-by: ScarKy0 <scarky0@onet.eu>
Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
Velken
2026-06-29 12:43:36 +02:00
committed by Coryler
co-authored by Killerqu00 Killerqu00 ScarKy0 Princess Cheeseballs
parent dd43910f79
commit 12a7a2de67
16 changed files with 785 additions and 637 deletions
@@ -17,6 +17,8 @@ using Robust.Shared.Random;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Numerics;
using Content.Shared.Damage.Systems;
using Robust.Shared.Prototypes;
using TimedDespawnComponent = Robust.Shared.Spawners.TimedDespawnComponent;
namespace Content.Server.Explosion.EntitySystems;
@@ -506,10 +508,11 @@ public sealed partial class ExplosionSystem
int maxTileBreak,
bool canCreateVacuum,
List<(Vector2i GridIndices, Tile Tile)> damagedTiles,
ExplosionPrototype type)
ExplosionPrototype type,
TileHistoryComponent? history,
ref (TileHistoryChunk? Chunk, Vector2i Indices)? chunk)
{
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef
|| tileDef.Indestructible)
if (_tileDefinitionManager[tileRef.Tile.TypeId] is not ContentTileDefinition tileDef || tileDef.Indestructible)
return;
if (!CanCreateVacuum)
@@ -517,18 +520,17 @@ public sealed partial class ExplosionSystem
else if (tileDef.MapAtmosphere)
canCreateVacuum = true; // is already a vacuum.
// break the tile into its underlying parts
int tileBreakages = 0;
while (maxTileBreak > tileBreakages && _robustRandom.Prob(type.TileBreakChance(effectiveIntensity)))
{
tileBreakages++;
effectiveIntensity -= type.TileBreakRerollReduction;
// does this have a base-turf that we can break it down to?
if (string.IsNullOrEmpty(tileDef.BaseTurf))
if (GetNextTile((tileDef, tileRef.GridIndices), history, ref chunk) is not { } newId)
break;
if (_tileDefinitionManager[tileDef.BaseTurf] is not ContentTileDefinition newDef)
break;
var newDef = (ContentTileDefinition) _tileDefinitionManager[newId];
if (newDef.MapAtmosphere && !canCreateVacuum)
break;
@@ -541,6 +543,44 @@ public sealed partial class ExplosionSystem
damagedTiles.Add((tileRef.GridIndices, new Tile(tileDef.TileId)));
}
private ProtoId<ContentTileDefinition>? GetNextTile((ContentTileDefinition tileDef, Vector2i gridIndices) tile,
TileHistoryComponent? history,
ref (TileHistoryChunk? Chunk, Vector2i Indices)? chunk)
{
if (chunk?.Chunk == null || !chunk.Value.Chunk.History.TryGetValue(tile.gridIndices, out var stack))
return tile.tileDef.BaseTurf; // No tile stack means we return BaseTurf if it exists!
// last entry in the stack
if (stack.Count > 1)
{
var newId = stack[^1];
stack.RemoveAt(stack.Count - 1);
chunk.Value.Chunk.LastModified = _timing.CurTick;
return newId;
}
chunk.Value.Chunk.History.Remove(tile.gridIndices);
if (chunk.Value.Chunk.History.Count == 0)
{
history?.ChunkHistory.Remove(chunk.Value.Indices);
chunk = null;
}
else
{
chunk.Value.Chunk.LastModified = _timing.CurTick;
}
return stack[0]; // If the stack is somehow empty, this will throw, but we will have at least removed it from dict first!
}
public void DirtyHistory(EntityUid grid)
{
if (!_tileHistoryQuery.TryComp(grid, out var history))
return;
Dirty(grid, history);
}
}
/// <summary>
@@ -573,11 +613,14 @@ sealed class Explosion
/// <summary>
/// The actual grid that this corresponds to. If null, this implies space.
/// </summary>
public Entity<MapGridComponent>? MapGrid;
public Entity<MapGridComponent, TileHistoryComponent?>? MapGrid;
}
private readonly List<ExplosionData> _explosionData = new();
private Entity<MapGridComponent, TileHistoryComponent?>? _currentGrid;
private (TileHistoryChunk? Chunk, Vector2i Indices)? _currentChunk;
/// <summary>
/// The explosion intensity associated with each tile iteration.
/// </summary>
@@ -625,7 +668,6 @@ sealed class Explosion
private DamageSpecifier? _expectedDamage;
#endif
private Entity<BroadphaseComponent> _currentLookup = default!;
private Entity<MapGridComponent>? _currentGrid;
private float _currentIntensity;
private float _currentThrowForce;
private List<Vector2i>.Enumerator _currentEnumerator;
@@ -691,7 +733,8 @@ sealed class Explosion
EntityUid visualEnt,
EntityUid? cause,
SharedMapSystem mapSystem,
Shared.Damage.Systems.DamageableSystem damageable)
DamageableSystem damageable,
EntityQuery<TileHistoryComponent> historyQuery)
{
VisualEnt = visualEnt;
Cause = cause;
@@ -722,7 +765,7 @@ sealed class Explosion
{
TileLists = spaceData.TileLists,
Lookup = (mapUid, entMan.GetComponent<BroadphaseComponent>(mapUid)),
MapGrid = null
MapGrid = null,
});
_spaceMatrix = spaceMatrix;
@@ -731,11 +774,12 @@ sealed class Explosion
foreach (var grid in gridData)
{
var history = historyQuery.CompOrNull(grid.Grid);
_explosionData.Add(new ExplosionData
{
TileLists = grid.TileLists,
Lookup = (grid.Grid, entMan.GetComponent<BroadphaseComponent>(grid.Grid)),
MapGrid = grid.Grid,
MapGrid = (grid.Grid, entMan.GetComponent<MapGridComponent>(grid.Grid), history),
});
}
@@ -784,10 +828,11 @@ sealed class Explosion
_currentEnumerator = tileList.GetEnumerator();
_currentLookup = _explosionData[_currentDataIndex].Lookup;
_currentGrid = _explosionData[_currentDataIndex].MapGrid;
_currentChunk = null;
_currentDataIndex++;
// sanity checks, in case something changed while the explosion was being processed over several ticks.
if (_currentLookup.Comp.Deleted || _currentGrid != null && !_entMan.EntityExists(_currentGrid.Value))
if (_currentLookup.Comp.Deleted || (_currentGrid is { } grid && !_entMan.EntityExists(grid.Owner)))
continue;
return true;
@@ -843,19 +888,19 @@ sealed class Explosion
// Is the current tile on a grid (instead of in space)?
if (_currentGrid is { } currentGrid &&
_mapSystem.TryGetTileRef(currentGrid, currentGrid.Comp, _currentEnumerator.Current, out var tileRef) &&
_mapSystem.TryGetTileRef(currentGrid.Owner, currentGrid.Comp1, _currentEnumerator.Current, out var tileRef) &&
!tileRef.Tile.IsEmpty)
{
if (!_tileUpdateDict.TryGetValue(currentGrid, out var tileUpdateList))
if (!_tileUpdateDict.TryGetValue((currentGrid.Owner, currentGrid.Comp1), out var tileUpdateList))
{
tileUpdateList = new();
_tileUpdateDict[currentGrid] = tileUpdateList;
_tileUpdateDict[(currentGrid.Owner, currentGrid.Comp1)] = tileUpdateList;
}
// damage entities on the tile. Also figures out whether there are any solid entities blocking the floor
// from being destroyed.
var canDamageFloor = _system.ExplodeTile(_currentLookup,
currentGrid,
(currentGrid.Owner, currentGrid.Comp1),
_currentEnumerator.Current,
_currentThrowForce,
_currentDamage,
@@ -869,7 +914,24 @@ sealed class Explosion
// If the floor is not blocked by some dense object, damage the floor tiles.
if (canDamageFloor)
_system.DamageFloorTile(tileRef, _currentIntensity * _tileBreakScale, _maxTileBreak, _canCreateVacuum, tileUpdateList, ExplosionType);
{
var tileIndices = _currentEnumerator.Current;
var chunkIndices = SharedMapSystem.GetChunkIndices(tileIndices, TileSystem.ChunkSize);
if (_currentChunk?.Indices != chunkIndices)
{
var chunk = currentGrid.Comp2?.ChunkHistory.GetValueOrDefault(chunkIndices);
_currentChunk = (chunk, chunkIndices);
}
_system.DamageFloorTile(tileRef,
_currentIntensity * _tileBreakScale,
_maxTileBreak,
_canCreateVacuum,
tileUpdateList,
ExplosionType,
currentGrid.Comp2,
ref _currentChunk);
}
}
else
{
@@ -909,6 +971,8 @@ sealed class Explosion
if (list.Count > 0 && _entMan.EntityExists(grid.Owner))
{
_mapSystem.SetTiles(grid.Owner, grid, list);
_system.DirtyHistory(grid.Owner);
}
}
_tileUpdateDict.Clear();