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:
parent
dd43910f79
commit
12a7a2de67
|
|
@ -194,7 +194,7 @@ public sealed class RCDTest : InteractionTest
|
|||
// Deconstruct the steel tile.
|
||||
await Interact(null, pEast);
|
||||
await RunSeconds(settingDeconstructTile.Delay + 1); // wait for the deconstruction to finish
|
||||
await AssertTile(Lattice, FromServer(pEast));
|
||||
await AssertTile(Plating, FromServer(pEast));
|
||||
|
||||
// Check that the cost of the deconstruction was subtracted from the current charges.
|
||||
newCharges = sCharges.GetCurrentCharges(ToServer(rcd));
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ public abstract partial class InteractionTest
|
|||
protected const string FloorItem = "FloorTileItemSteel";
|
||||
protected const string Plating = "Plating";
|
||||
protected const string Lattice = "Lattice";
|
||||
protected const string PlatingBrass = "PlatingBrass";
|
||||
|
||||
// Structures
|
||||
protected const string Airlock = "Airlock";
|
||||
|
|
|
|||
|
|
@ -100,4 +100,25 @@ public sealed class TileConstructionTests : InteractionTest
|
|||
|
||||
await AssertEntityLookup((FloorItem, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test brassPlating -> floor -> brassPlating using tilestacking
|
||||
/// </summary>
|
||||
[Test]
|
||||
public async Task BrassPlatingPlace()
|
||||
{
|
||||
await SetTile(PlatingBrass);
|
||||
|
||||
// Brass Plating -> Tile
|
||||
await InteractUsing(FloorItem);
|
||||
Assert.That(HandSys.GetActiveItem((SEntMan.GetEntity(Player), Hands)), Is.Null);
|
||||
await AssertTile(Floor);
|
||||
AssertGridCount(1);
|
||||
|
||||
// Tile -> Brass Plating
|
||||
await InteractUsing(Pry);
|
||||
await AssertTile(PlatingBrass);
|
||||
AssertGridCount(1);
|
||||
await AssertEntityLookup((FloorItem, 1));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.IntegrationTests.Tests.Tiles;
|
||||
|
||||
public sealed class TileStackRecursionTest
|
||||
{
|
||||
[Test]
|
||||
public async Task TestBaseTurfRecursion()
|
||||
{
|
||||
await using var pair = await PoolManager.GetServerClient();
|
||||
var protoMan = pair.Server.ResolveDependency<IPrototypeManager>();
|
||||
var cfg = pair.Server.ResolveDependency<IConfigurationManager>();
|
||||
var maxTileHistoryLength = cfg.GetCVar(CCVars.TileStackLimit);
|
||||
Assert.That(protoMan.TryGetInstances<ContentTileDefinition>(out var tiles));
|
||||
Assert.That(tiles, Is.Not.EqualTo(null));
|
||||
//store the distance from the root node to the given tile node
|
||||
var nodes = new List<(ProtoId<ContentTileDefinition>, int)>();
|
||||
//each element of list is a connection from BaseTurf tile to tile that goes on it
|
||||
var edges = new List<(ProtoId<ContentTileDefinition>, ProtoId<ContentTileDefinition>)>();
|
||||
foreach (var ctdef in tiles!.Values)
|
||||
{
|
||||
//at first, each node is unexplored and has infinite distance to root.
|
||||
//we use space node as root - everything is supposed to start at space, and it's hardcoded into the game anyway.
|
||||
if (ctdef.ID == ContentTileDefinition.SpaceID)
|
||||
{
|
||||
nodes.Insert(0, (ctdef.ID, 0)); //space is the first element
|
||||
continue;
|
||||
}
|
||||
Assert.That(ctdef.BaseTurf != ctdef.ID);
|
||||
nodes.Add((ctdef.ID, int.MaxValue));
|
||||
if (ctdef.BaseTurf != null)
|
||||
edges.Add((ctdef.BaseTurf.Value, ctdef.ID));
|
||||
Assert.That(ctdef.BaseWhitelist, Does.Not.Contain(ctdef.ID));
|
||||
edges.AddRange(ctdef.BaseWhitelist.Select(possibleTurf =>
|
||||
(possibleTurf, new ProtoId<ContentTileDefinition>(ctdef.ID))));
|
||||
}
|
||||
Bfs(nodes, edges, maxTileHistoryLength);
|
||||
await pair.CleanReturnAsync();
|
||||
}
|
||||
|
||||
private void Bfs(List<(ProtoId<ContentTileDefinition>, int)> nodes, List<(ProtoId<ContentTileDefinition>, ProtoId<ContentTileDefinition>)> edges, int depthLimit)
|
||||
{
|
||||
var root = nodes[0];
|
||||
var queue = new Queue<(ProtoId<ContentTileDefinition>, int)>();
|
||||
queue.Enqueue(root);
|
||||
while (queue.Count != 0)
|
||||
{
|
||||
var u = queue.Dequeue();
|
||||
//get a list of tiles that can be put on this tile
|
||||
var adj = edges.Where(n => n.Item1 == u.Item1).Select(n => n.Item2);
|
||||
var adjNodes = nodes.Where(n => adj.Contains(n.Item1)).ToList();
|
||||
foreach (var node in adjNodes)
|
||||
{
|
||||
var adjNode = node;
|
||||
adjNode.Item2 = u.Item2 + 1;
|
||||
Assert.That(adjNode.Item2, Is.LessThanOrEqualTo(depthLimit)); //we can doomstack tiles on top of each other. Bad!
|
||||
queue.Enqueue(adjNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ using Content.Shared.Explosion.Components;
|
|||
using Content.Shared.Explosion.EntitySystems;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.Maps;
|
||||
using Content.Shared.Projectiles;
|
||||
using Content.Shared.Throwing;
|
||||
using Robust.Server.GameStates;
|
||||
|
|
@ -30,6 +31,7 @@ using Robust.Shared.Physics.Components;
|
|||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.Explosion.EntitySystems;
|
||||
|
|
@ -47,6 +49,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageableSystem = default!;
|
||||
|
|
@ -70,6 +73,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||
private EntityQuery<DestructibleComponent> _destructibleQuery;
|
||||
private EntityQuery<DamageableComponent> _damageableQuery;
|
||||
private EntityQuery<AirtightComponent> _airtightQuery;
|
||||
private EntityQuery<TileHistoryComponent> _tileHistoryQuery;
|
||||
|
||||
/// <summary>
|
||||
/// "Tile-size" for space when there are no nearby grids to use as a reference.
|
||||
|
|
@ -112,6 +116,7 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||
_destructibleQuery = GetEntityQuery<DestructibleComponent>();
|
||||
_damageableQuery = GetEntityQuery<DamageableComponent>();
|
||||
_airtightQuery = GetEntityQuery<AirtightComponent>();
|
||||
_tileHistoryQuery = GetEntityQuery<TileHistoryComponent>();
|
||||
|
||||
_prototypeManager.PrototypesReloaded += ReloadExplosionPrototypes;
|
||||
}
|
||||
|
|
@ -431,7 +436,8 @@ public sealed partial class ExplosionSystem : SharedExplosionSystem
|
|||
visualEnt,
|
||||
queued.Cause,
|
||||
_map,
|
||||
_damageableSystem);
|
||||
_damageableSystem,
|
||||
_tileHistoryQuery);
|
||||
}
|
||||
|
||||
private void CameraShake(List<float> rangeList, MapCoordinates epicenter, QueuedExplosion queued) // Starlight - replace range with rangeList, totalIntensity with queued
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Numerics;
|
||||
using Content.Shared.Maps;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Maps;
|
||||
|
||||
/// <summary>
|
||||
/// This system handles transferring <see cref="TileHistoryComponent"/> data when a grid is split.
|
||||
/// </summary>
|
||||
public sealed class TileGridSplitSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GridSplitEvent>(OnGridSplit);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Transfer tile history from the old grid to the new grids.
|
||||
/// </summary>
|
||||
private void OnGridSplit(ref GridSplitEvent ev)
|
||||
{
|
||||
if (!TryComp<TileHistoryComponent>(ev.Grid, out var oldHistory))
|
||||
return;
|
||||
|
||||
var oldGrid = Comp<MapGridComponent>(ev.Grid);
|
||||
|
||||
foreach (var gridUid in ev.NewGrids)
|
||||
{
|
||||
// ensure the new grid has a history component and get its grid component
|
||||
var newHistory = EnsureComp<TileHistoryComponent>(gridUid);
|
||||
var newGrid = Comp<MapGridComponent>(gridUid);
|
||||
|
||||
foreach (var tile in _maps.GetAllTiles(gridUid, newGrid))
|
||||
{
|
||||
// calculate where this tile was on the old grid
|
||||
var oldIndices = _maps.LocalToTile(ev.Grid, oldGrid, new EntityCoordinates(gridUid, new Vector2(tile.GridIndices.X + 0.5f, tile.GridIndices.Y + 0.5f)));
|
||||
|
||||
var chunkIndices = SharedMapSystem.GetChunkIndices(oldIndices, TileSystem.ChunkSize);
|
||||
if (oldHistory.ChunkHistory.TryGetValue(chunkIndices, out var oldChunk) &&
|
||||
oldChunk.History.TryGetValue(oldIndices, out var history))
|
||||
{
|
||||
// now we move the history from the old grid to the new grid
|
||||
var newChunkIndices = SharedMapSystem.GetChunkIndices(tile.GridIndices, TileSystem.ChunkSize);
|
||||
if (!newHistory.ChunkHistory.TryGetValue(newChunkIndices, out var newChunk))
|
||||
{
|
||||
newChunk = new TileHistoryChunk();
|
||||
newHistory.ChunkHistory[newChunkIndices] = newChunk;
|
||||
}
|
||||
|
||||
newChunk.History[tile.GridIndices] = new List<ProtoId<ContentTileDefinition>>(history);
|
||||
newChunk.LastModified = _timing.CurTick;
|
||||
|
||||
// clean up the old history
|
||||
oldChunk.History.Remove(oldIndices);
|
||||
if (oldChunk.History.Count == 0)
|
||||
oldHistory.ChunkHistory.Remove(chunkIndices);
|
||||
else
|
||||
oldChunk.LastModified = _timing.CurTick;
|
||||
}
|
||||
}
|
||||
|
||||
Dirty(gridUid, newHistory);
|
||||
}
|
||||
|
||||
Dirty(ev.Grid, oldHistory);
|
||||
}
|
||||
}
|
||||
|
|
@ -409,4 +409,13 @@ public sealed partial class CCVars
|
|||
/// </summary>
|
||||
public static readonly CVarDef<bool> GameHostnameInTitlebar =
|
||||
CVarDef.Create("game.hostname_in_titlebar", true, CVar.SERVER | CVar.REPLICATED);
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of tiles you can stack on top of each other. 0 is unlimited.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Having it too high can result in "doomstacking" tiles - this messes with efficiency of explosions, deconstruction of tiles, and might result in memory problems.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<int> TileStackLimit =
|
||||
CVarDef.Create("game.tile_stack_limit", 5, CVar.SERVER | CVar.REPLICATED);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using Robust.Shared.Prototypes;
|
|||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Maps
|
||||
|
|
@ -41,7 +42,13 @@ namespace Content.Shared.Maps
|
|||
[DataField("isSubfloor")] public bool IsSubFloor { get; private set; }
|
||||
|
||||
[DataField("baseTurf")]
|
||||
public string BaseTurf { get; private set; } = string.Empty;
|
||||
public ProtoId<ContentTileDefinition>? BaseTurf { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// On what tiles this tile can be placed on. BaseTurf is already included.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<ProtoId<ContentTileDefinition>> BaseWhitelist { get; private set; } = new();
|
||||
|
||||
[DataField]
|
||||
public PrototypeFlags<ToolQualityPrototype> DeconstructTools { get; set; } = new();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,125 @@
|
|||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Maps;
|
||||
|
||||
[RegisterComponent, NetworkedComponent]
|
||||
public sealed partial class TileHistoryComponent : Component
|
||||
{
|
||||
// History of tiles for each grid chunk.
|
||||
[DataField]
|
||||
public Dictionary<Vector2i, TileHistoryChunk> ChunkHistory = new();
|
||||
|
||||
/// <summary>
|
||||
/// Tick at which PVS was last toggled. Ensures that all players receive a full update when toggling PVS.
|
||||
/// </summary>
|
||||
public GameTick ForceTick { get; set; }
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TileHistoryState : ComponentState
|
||||
{
|
||||
public Dictionary<Vector2i, TileHistoryChunk> ChunkHistory;
|
||||
|
||||
public TileHistoryState(Dictionary<Vector2i, TileHistoryChunk> chunkHistory)
|
||||
{
|
||||
ChunkHistory = chunkHistory;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public sealed class TileHistoryDeltaState : ComponentState, IComponentDeltaState<TileHistoryState>
|
||||
{
|
||||
public Dictionary<Vector2i, TileHistoryChunk> ChunkHistory;
|
||||
public HashSet<Vector2i> AllHistoryChunks;
|
||||
|
||||
public TileHistoryDeltaState(Dictionary<Vector2i, TileHistoryChunk> chunkHistory, HashSet<Vector2i> allHistoryChunks)
|
||||
{
|
||||
ChunkHistory = chunkHistory;
|
||||
AllHistoryChunks = allHistoryChunks;
|
||||
}
|
||||
|
||||
public void ApplyToFullState(TileHistoryState state)
|
||||
{
|
||||
var toRemove = new List<Vector2i>();
|
||||
foreach (var key in state.ChunkHistory.Keys)
|
||||
{
|
||||
if (!AllHistoryChunks.Contains(key))
|
||||
toRemove.Add(key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
state.ChunkHistory.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (indices, chunk) in ChunkHistory)
|
||||
{
|
||||
state.ChunkHistory[indices] = new TileHistoryChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyToComponent(TileHistoryComponent component)
|
||||
{
|
||||
var toRemove = new List<Vector2i>();
|
||||
foreach (var key in component.ChunkHistory.Keys)
|
||||
{
|
||||
if (!AllHistoryChunks.Contains(key))
|
||||
toRemove.Add(key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
component.ChunkHistory.Remove(key);
|
||||
}
|
||||
|
||||
foreach (var (indices, chunk) in ChunkHistory)
|
||||
{
|
||||
component.ChunkHistory[indices] = new TileHistoryChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public TileHistoryState CreateNewFullState(TileHistoryState state)
|
||||
{
|
||||
var chunks = new Dictionary<Vector2i, TileHistoryChunk>(state.ChunkHistory.Count);
|
||||
|
||||
foreach (var (indices, chunk) in ChunkHistory)
|
||||
{
|
||||
chunks[indices] = new TileHistoryChunk(chunk);
|
||||
}
|
||||
|
||||
foreach (var (indices, chunk) in state.ChunkHistory)
|
||||
{
|
||||
if (AllHistoryChunks.Contains(indices))
|
||||
chunks.TryAdd(indices, new TileHistoryChunk(chunk));
|
||||
}
|
||||
|
||||
return new TileHistoryState(chunks);
|
||||
}
|
||||
}
|
||||
|
||||
[DataDefinition, Serializable, NetSerializable]
|
||||
public sealed partial class TileHistoryChunk
|
||||
{
|
||||
[DataField]
|
||||
public Dictionary<Vector2i, List<ProtoId<ContentTileDefinition>>> History = new();
|
||||
|
||||
[ViewVariables]
|
||||
public GameTick LastModified;
|
||||
|
||||
public TileHistoryChunk()
|
||||
{
|
||||
}
|
||||
|
||||
public TileHistoryChunk(TileHistoryChunk other)
|
||||
{
|
||||
History = new Dictionary<Vector2i, List<ProtoId<ContentTileDefinition>>>(other.History.Count);
|
||||
foreach (var (key, value) in other.History)
|
||||
{
|
||||
History[key] = new List<ProtoId<ContentTileDefinition>>(value);
|
||||
}
|
||||
LastModified = other.LastModified;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,16 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Coordinates.Helpers;
|
||||
using Content.Shared.Decals;
|
||||
using Content.Shared.Tiles;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Map.Components;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Maps;
|
||||
|
|
@ -14,12 +20,85 @@ namespace Content.Shared.Maps;
|
|||
/// </summary>
|
||||
public sealed class TileSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
|
||||
[Dependency] private readonly SharedDecalSystem _decal = default!;
|
||||
[Dependency] private readonly SharedMapSystem _maps = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public const int ChunkSize = 16;
|
||||
|
||||
private int _tileStackLimit;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
SubscribeLocalEvent<GridInitializeEvent>(OnGridStartup);
|
||||
SubscribeLocalEvent<TileHistoryComponent, ComponentGetState>(OnGetState);
|
||||
SubscribeLocalEvent<TileHistoryComponent, ComponentHandleState>(OnHandleState);
|
||||
SubscribeLocalEvent<TileHistoryComponent, FloorTileAttemptEvent>(OnFloorTileAttempt);
|
||||
|
||||
_cfg.OnValueChanged(CCVars.TileStackLimit, t => _tileStackLimit = t, true);
|
||||
}
|
||||
|
||||
private void OnHandleState(EntityUid uid, TileHistoryComponent component, ref ComponentHandleState args)
|
||||
{
|
||||
if (args.Current is not TileHistoryState state && args.Current is not TileHistoryDeltaState)
|
||||
return;
|
||||
|
||||
if (args.Current is TileHistoryState fullState)
|
||||
{
|
||||
component.ChunkHistory.Clear();
|
||||
foreach (var (key, value) in fullState.ChunkHistory)
|
||||
{
|
||||
component.ChunkHistory[key] = new TileHistoryChunk(value);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.Current is TileHistoryDeltaState deltaState)
|
||||
{
|
||||
deltaState.ApplyToComponent(component);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGetState(EntityUid uid, TileHistoryComponent component, ref ComponentGetState args)
|
||||
{
|
||||
if (args.FromTick <= component.CreationTick || args.FromTick <= component.ForceTick)
|
||||
{
|
||||
var fullHistory = new Dictionary<Vector2i, TileHistoryChunk>(component.ChunkHistory.Count);
|
||||
foreach (var (key, value) in component.ChunkHistory)
|
||||
{
|
||||
fullHistory[key] = new TileHistoryChunk(value);
|
||||
}
|
||||
args.State = new TileHistoryState(fullHistory);
|
||||
return;
|
||||
}
|
||||
|
||||
var data = new Dictionary<Vector2i, TileHistoryChunk>();
|
||||
foreach (var (index, chunk) in component.ChunkHistory)
|
||||
{
|
||||
if (chunk.LastModified >= args.FromTick)
|
||||
data[index] = new TileHistoryChunk(chunk);
|
||||
}
|
||||
|
||||
args.State = new TileHistoryDeltaState(data, new(component.ChunkHistory.Keys));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// On grid startup, ensure that we have Tile History.
|
||||
/// </summary>
|
||||
private void OnGridStartup(GridInitializeEvent ev)
|
||||
{
|
||||
if (HasComp<MapComponent>(ev.EntityUid))
|
||||
return;
|
||||
|
||||
EnsureComp<TileHistoryComponent>(ev.EntityUid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a weighted pick of a tile variant.
|
||||
|
|
@ -85,7 +164,7 @@ public sealed class TileSystem : EntitySystem
|
|||
return PryTile(tileRef);
|
||||
}
|
||||
|
||||
public bool PryTile(TileRef tileRef)
|
||||
public bool PryTile(TileRef tileRef)
|
||||
{
|
||||
return PryTile(tileRef, false);
|
||||
}
|
||||
|
|
@ -97,7 +176,7 @@ public sealed class TileSystem : EntitySystem
|
|||
if (tile.IsEmpty)
|
||||
return false;
|
||||
|
||||
var tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.TypeId];
|
||||
var tileDef = (ContentTileDefinition)_tileDefinitionManager[tile.TypeId];
|
||||
|
||||
if (!tileDef.CanCrowbar)
|
||||
return false;
|
||||
|
|
@ -112,33 +191,73 @@ public sealed class TileSystem : EntitySystem
|
|||
return ReplaceTile(tileref, replacementTile, tileref.GridUid, grid);
|
||||
}
|
||||
|
||||
public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = null)
|
||||
public bool ReplaceTile(TileRef tileref, ContentTileDefinition replacementTile, EntityUid grid, MapGridComponent? component = null, byte? variant = null)
|
||||
{
|
||||
DebugTools.Assert(tileref.GridUid == grid);
|
||||
|
||||
if (!Resolve(grid, ref component))
|
||||
return false;
|
||||
|
||||
var key = tileref.GridIndices;
|
||||
var currentTileDef = (ContentTileDefinition) _tileDefinitionManager[tileref.Tile.TypeId];
|
||||
|
||||
var variant = PickVariant(replacementTile);
|
||||
// If the tile we're placing has a baseTurf that matches the tile we're replacing, we don't need to create a history
|
||||
// unless the tile already has a history.
|
||||
var history = EnsureComp<TileHistoryComponent>(grid);
|
||||
var chunkIndices = SharedMapSystem.GetChunkIndices(key, ChunkSize);
|
||||
history.ChunkHistory.TryGetValue(chunkIndices, out var chunk);
|
||||
var historyExists = chunk != null && chunk.History.ContainsKey(key);
|
||||
|
||||
if (replacementTile.BaseTurf != currentTileDef.ID || historyExists)
|
||||
{
|
||||
if (chunk == null)
|
||||
{
|
||||
chunk = new TileHistoryChunk();
|
||||
history.ChunkHistory[chunkIndices] = chunk;
|
||||
}
|
||||
|
||||
chunk.LastModified = _timing.CurTick;
|
||||
Dirty(grid, history);
|
||||
|
||||
//Create stack if needed
|
||||
if (!chunk.History.TryGetValue(key, out var stack))
|
||||
{
|
||||
stack = new List<ProtoId<ContentTileDefinition>>();
|
||||
chunk.History[key] = stack;
|
||||
}
|
||||
|
||||
//Prevent the doomstack
|
||||
if (stack.Count >= _tileStackLimit && _tileStackLimit != 0)
|
||||
return false;
|
||||
|
||||
//Push current tile to the stack, if not empty
|
||||
if (!tileref.Tile.IsEmpty)
|
||||
{
|
||||
stack.Add(currentTileDef.ID);
|
||||
}
|
||||
}
|
||||
|
||||
variant ??= PickVariant(replacementTile);
|
||||
var decals = _decal.GetDecalsInRange(tileref.GridUid, _turf.GetTileCenter(tileref).Position, 0.5f);
|
||||
foreach (var (id, _) in decals)
|
||||
{
|
||||
_decal.RemoveDecal(tileref.GridUid, id);
|
||||
}
|
||||
|
||||
_maps.SetTile(grid, component, tileref.GridIndices, new Tile(replacementTile.TileId, 0, variant));
|
||||
_maps.SetTile(grid, component, tileref.GridIndices, new Tile(replacementTile.TileId, 0, variant.Value));
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool DeconstructTile(TileRef tileRef)
|
||||
|
||||
public bool DeconstructTile(TileRef tileRef, bool spawnItem = true)
|
||||
{
|
||||
if (tileRef.Tile.IsEmpty)
|
||||
return false;
|
||||
|
||||
var tileDef = (ContentTileDefinition) _tileDefinitionManager[tileRef.Tile.TypeId];
|
||||
var tileDef = (ContentTileDefinition)_tileDefinitionManager[tileRef.Tile.TypeId];
|
||||
|
||||
if (string.IsNullOrEmpty(tileDef.BaseTurf))
|
||||
//Can't deconstruct anything that doesn't have a base turf.
|
||||
if (tileDef.BaseTurf == null)
|
||||
return false;
|
||||
|
||||
var gridUid = tileRef.GridUid;
|
||||
|
|
@ -152,20 +271,68 @@ public sealed class TileSystem : EntitySystem
|
|||
(_robustRandom.NextFloat() - 0.5f) * bounds,
|
||||
(_robustRandom.NextFloat() - 0.5f) * bounds));
|
||||
|
||||
//Actually spawn the relevant tile item at the right position and give it some random offset.
|
||||
var tileItem = Spawn(tileDef.ItemDropPrototypeName, coordinates);
|
||||
Transform(tileItem).LocalRotation = _robustRandom.NextDouble() * Math.Tau;
|
||||
var historyComp = EnsureComp<TileHistoryComponent>(gridUid);
|
||||
ProtoId<ContentTileDefinition> previousTileId;
|
||||
|
||||
// Destroy any decals on the tile
|
||||
var chunkIndices = SharedMapSystem.GetChunkIndices(indices, ChunkSize);
|
||||
|
||||
//Pop from stack if we have history
|
||||
if (historyComp.ChunkHistory.TryGetValue(chunkIndices, out var chunk) &&
|
||||
chunk.History.TryGetValue(indices, out var stack) && stack.Count > 0)
|
||||
{
|
||||
chunk.LastModified = _timing.CurTick;
|
||||
Dirty(gridUid, historyComp);
|
||||
|
||||
previousTileId = stack.Last();
|
||||
stack.RemoveAt(stack.Count - 1);
|
||||
|
||||
//Clean up empty stacks to avoid memory buildup
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
chunk.History.Remove(indices);
|
||||
}
|
||||
|
||||
// Clean up empty chunks
|
||||
if (chunk.History.Count == 0)
|
||||
{
|
||||
historyComp.ChunkHistory.Remove(chunkIndices);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//No stack? Assume BaseTurf was the layer below
|
||||
previousTileId = tileDef.BaseTurf.Value;
|
||||
}
|
||||
|
||||
if (spawnItem)
|
||||
{
|
||||
//Actually spawn the relevant tile item at the right position and give it some random offset.
|
||||
var tileItem = Spawn(tileDef.ItemDropPrototypeName, coordinates);
|
||||
Transform(tileItem).LocalRotation = _robustRandom.NextDouble() * Math.Tau;
|
||||
}
|
||||
|
||||
//Destroy any decals on the tile
|
||||
var decals = _decal.GetDecalsInRange(gridUid, coordinates.SnapToGrid(EntityManager, _mapManager).Position, 0.5f);
|
||||
foreach (var (id, _) in decals)
|
||||
{
|
||||
_decal.RemoveDecal(tileRef.GridUid, id);
|
||||
}
|
||||
|
||||
var plating = _tileDefinitionManager[tileDef.BaseTurf];
|
||||
_maps.SetTile(gridUid, mapGrid, tileRef.GridIndices, new Tile(plating.TileId));
|
||||
//Replace tile with the one it was placed on
|
||||
var previousDef = (ContentTileDefinition)_tileDefinitionManager[previousTileId];
|
||||
_maps.SetTile(gridUid, mapGrid, indices, new Tile(previousDef.TileId));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnFloorTileAttempt(Entity<TileHistoryComponent> ent, ref FloorTileAttemptEvent args)
|
||||
{
|
||||
if (_tileStackLimit == 0)
|
||||
return;
|
||||
var chunkIndices = SharedMapSystem.GetChunkIndices(args.GridIndices, ChunkSize);
|
||||
if (!ent.Comp.ChunkHistory.TryGetValue(chunkIndices, out var chunk) ||
|
||||
!chunk.History.TryGetValue(args.GridIndices, out var stack))
|
||||
return;
|
||||
args.Cancelled = stack.Count >= _tileStackLimit; // greater or equals because the attempt itself counts as a tile we're trying to place
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ public sealed class RCDSystem : EntitySystem
|
|||
[Dependency] private readonly SharedInteractionSystem _interaction = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly TurfSystem _turf = default!;
|
||||
[Dependency] private readonly TileSystem _tile = default!;
|
||||
[Dependency] private readonly EntityLookupSystem _lookup = default!;
|
||||
[Dependency] private readonly IPrototypeManager _protoManager = default!;
|
||||
[Dependency] private readonly SharedMapSystem _mapSystem = default!;
|
||||
|
|
@ -569,10 +570,9 @@ public sealed class RCDSystem : EntitySystem
|
|||
|
||||
if (target == null)
|
||||
{
|
||||
// Deconstruct tile (either converts the tile to lattice, or removes lattice)
|
||||
var tileDef = (_turf.GetContentTileDefinition(tile).ID != "Lattice") ? new Tile(_tileDefMan["Lattice"].TileId) : Tile.Empty;
|
||||
_mapSystem.SetTile(gridUid, mapGrid, position, tileDef);
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} tile: {position} open to space");
|
||||
// Deconstruct tile, don't drop tile as item
|
||||
if (_tile.DeconstructTile(tile, spawnItem: false))
|
||||
_adminLogger.Add(LogType.RCD, LogImpact.High, $"{ToPrettyString(user):user} used RCD to set grid: {gridUid} tile: {position} open to space");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ using Robust.Shared.Network;
|
|||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Components;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.Tiles;
|
||||
|
|
@ -142,7 +143,7 @@ public sealed class FloorTileSystem : EntitySystem
|
|||
|
||||
var baseTurf = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
|
||||
|
||||
if (HasBaseTurf(currentTileDefinition, baseTurf.ID))
|
||||
if (CanPlaceOn(currentTileDefinition, baseTurf.ID))
|
||||
{
|
||||
if (!_stackSystem.TryUse((uid, stack), 1))
|
||||
continue;
|
||||
|
|
@ -152,7 +153,7 @@ public sealed class FloorTileSystem : EntitySystem
|
|||
return;
|
||||
}
|
||||
}
|
||||
else if (HasBaseTurf(currentTileDefinition, ContentTileDefinition.SpaceID))
|
||||
else if (HasBaseTurf(currentTileDefinition, new ProtoId<ContentTileDefinition>(ContentTileDefinition.SpaceID)))
|
||||
{
|
||||
if (!_stackSystem.TryUse((uid, stack), 1))
|
||||
continue;
|
||||
|
|
@ -171,19 +172,35 @@ public sealed class FloorTileSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
public bool HasBaseTurf(ContentTileDefinition tileDef, string baseTurf)
|
||||
public bool HasBaseTurf(ContentTileDefinition tileDef, ProtoId<ContentTileDefinition> baseTurf)
|
||||
{
|
||||
return tileDef.BaseTurf == baseTurf;
|
||||
}
|
||||
|
||||
private bool CanPlaceOn(ContentTileDefinition tileDef, ProtoId<ContentTileDefinition> currentTurfId)
|
||||
{
|
||||
//Check exact BaseTurf match
|
||||
if (tileDef.BaseTurf == currentTurfId)
|
||||
return true;
|
||||
|
||||
// Check whitelist match
|
||||
if (tileDef.BaseWhitelist.Count > 0 && tileDef.BaseWhitelist.Contains(currentTurfId))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void PlaceAt(EntityUid user, EntityUid gridUid, MapGridComponent mapGrid, EntityCoordinates location,
|
||||
ushort tileId, SoundSpecifier placeSound, float offset = 0)
|
||||
{
|
||||
_adminLogger.Add(LogType.Tile, LogImpact.Low, $"{ToPrettyString(user):actor} placed tile {_tileDefinitionManager[tileId].Name} at {ToPrettyString(gridUid)} {location}");
|
||||
|
||||
var random = new System.Random((int) _timing.CurTick.Value);
|
||||
var variant = _tile.PickVariant((ContentTileDefinition) _tileDefinitionManager[tileId], random);
|
||||
_map.SetTile(gridUid, mapGrid,location.Offset(new Vector2(offset, offset)), new Tile(tileId, 0, variant));
|
||||
var tileDef = (ContentTileDefinition) _tileDefinitionManager[tileId];
|
||||
var random = new System.Random((int)_timing.CurTick.Value);
|
||||
var variant = _tile.PickVariant(tileDef, random);
|
||||
|
||||
var tileRef = _map.GetTileRef(gridUid, mapGrid, location.Offset(new Vector2(offset, offset)));
|
||||
_tile.ReplaceTile(tileRef, tileDef, gridUid, mapGrid, variant: variant);
|
||||
|
||||
_audio.PlayPredicted(placeSound, location, user);
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,5 +1,16 @@
|
|||
- type: tile
|
||||
id: BaseFloorPlanet
|
||||
abstract: true
|
||||
heatCapacity: 10000
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
- type: tile
|
||||
id: FloorPlanetDirt
|
||||
parent: BaseFloorPlanet
|
||||
name: tiles-dirt-planet-floor
|
||||
sprite: /Textures/Tiles/dirt.png # DeltaV - Was /Textures/Tiles/Planet/dirt.rsi/dirt.png, this breaks.
|
||||
variants: 4
|
||||
|
|
@ -8,16 +19,11 @@
|
|||
- 1.0
|
||||
- 1.0
|
||||
- 1.0
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
# Desert
|
||||
- type: tile
|
||||
id: FloorDesert
|
||||
parent: BaseFloorPlanet
|
||||
name: tiles-desert-floor
|
||||
sprite: /Textures/Tiles/Planet/Desert/desert.png
|
||||
variants: 6
|
||||
|
|
@ -28,12 +34,6 @@
|
|||
- 1.0
|
||||
- 1.0
|
||||
- 1.0
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
- type: tile
|
||||
id: FloorLowDesert
|
||||
|
|
@ -47,16 +47,11 @@
|
|||
- 1.0
|
||||
- 1.0
|
||||
- 1.0
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
# Grass
|
||||
- type: tile
|
||||
id: FloorPlanetGrass
|
||||
parent: BaseFloorPlanet
|
||||
name: tiles-grass-planet-floor
|
||||
sprite: /Textures/Tiles/Planet/Grass/grass.png
|
||||
variants: 4
|
||||
|
|
@ -76,29 +71,20 @@
|
|||
North: /Textures/Tiles/Planet/Grass/double_edge.png
|
||||
West: /Textures/Tiles/Planet/Grass/double_edge.png
|
||||
baseTurf: FloorPlanetDirt
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepGrass
|
||||
itemDrop: FloorTileItemGrass
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
# Lava
|
||||
- type: tile
|
||||
id: FloorBasalt
|
||||
name: tiles-basalt-floor
|
||||
parent: BaseFloorPlanet
|
||||
sprite: /Textures/Tiles/Planet/basalt.png
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepAsteroid
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
# Snow
|
||||
- type: tile
|
||||
id: FloorSnow
|
||||
parent: BaseFloorPlanet
|
||||
name: tiles-snow
|
||||
sprite: /Textures/Tiles/Planet/Snow/snow.png
|
||||
variants: 13
|
||||
|
|
@ -126,9 +112,6 @@
|
|||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepSnow
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
# Ice
|
||||
- type: tile
|
||||
|
|
@ -145,6 +128,7 @@
|
|||
# Dug snow
|
||||
- type: tile
|
||||
id: FloorSnowDug
|
||||
parent: BaseFloorPlanet
|
||||
name: tiles-snow-dug
|
||||
sprite: /Textures/Tiles/Planet/Snow/snow_dug.png
|
||||
edgeSpritePriority: 1
|
||||
|
|
@ -153,11 +137,7 @@
|
|||
East: /Textures/Tiles/Planet/Snow/snow_dug_double_edge_east.png
|
||||
North: /Textures/Tiles/Planet/Snow/snow_dug_double_edge_north.png
|
||||
West: /Textures/Tiles/Planet/Snow/snow_dug_double_edge_west.png
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepSnow
|
||||
heatCapacity: 10000
|
||||
weather: true
|
||||
indestructible: true
|
||||
|
||||
# Wasteland
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
- type: tile
|
||||
id: Plating
|
||||
name: tiles-plating
|
||||
sprite: /Textures/Tiles/plating.png
|
||||
baseTurf: Lattice
|
||||
id: BasePlating
|
||||
abstract: true
|
||||
friction: 1.5
|
||||
heatCapacity: 10000
|
||||
isSubfloor: true
|
||||
deconstructTools: [ Axing ] # DeltaV - The return of fireaxe prying
|
||||
footstepSounds:
|
||||
collection: FootstepPlating
|
||||
baseTurf: Lattice
|
||||
baseWhitelist:
|
||||
- TrainLattice
|
||||
|
||||
- type: tile
|
||||
id: Plating
|
||||
|
|
@ -16,6 +19,7 @@
|
|||
|
||||
- type: tile
|
||||
id: PlatingDamaged
|
||||
parent: BasePlating
|
||||
name: tiles-plating
|
||||
sprite: /Textures/Tiles/plating_damaged.png
|
||||
variants: 3
|
||||
|
|
@ -23,45 +27,25 @@
|
|||
- 1.0
|
||||
- 1.0
|
||||
- 1.0
|
||||
baseTurf: Lattice
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepPlating
|
||||
friction: 1.5
|
||||
heatCapacity: 10000
|
||||
|
||||
- type: tile
|
||||
id: PlatingAsteroid
|
||||
parent: BasePlating
|
||||
name: tiles-asteroid-plating
|
||||
sprite: /Textures/Tiles/Asteroid/asteroid_plating.png
|
||||
baseTurf: Lattice
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepPlating
|
||||
friction: 1.5
|
||||
heatCapacity: 10000
|
||||
|
||||
- type: tile
|
||||
id: PlatingBrass
|
||||
parent: BasePlating
|
||||
name: tiles-brass-plating
|
||||
sprite: /Textures/Tiles/Misc/clockwork/clockwork_floor.png
|
||||
baseTurf: Lattice
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepPlating
|
||||
friction: 1.5
|
||||
heatCapacity: 10000
|
||||
|
||||
- type: tile
|
||||
id: PlatingSnow
|
||||
name: tiles-snow-plating
|
||||
parent: BasePlating
|
||||
sprite: /Textures/Tiles/snow_plating.png #Not in the snow planet RSI because it doesn't have any metadata. Should probably be moved to its own folder later.
|
||||
baseTurf: Lattice
|
||||
isSubfloor: true
|
||||
footstepSounds:
|
||||
collection: FootstepPlating
|
||||
friction: 0.75 #a little less then actual snow
|
||||
heatCapacity: 10000
|
||||
|
||||
- type: tile
|
||||
id: PlatingIronsand
|
||||
|
|
@ -92,16 +76,8 @@
|
|||
|
||||
- type: tile
|
||||
id: TrainLattice
|
||||
parent: Lattice
|
||||
name: tiles-lattice-train
|
||||
sprite: /Textures/Tiles/latticeTrain.png
|
||||
baseTurf: Space
|
||||
isSubfloor: true
|
||||
deconstructTools: [ Cutting ]
|
||||
weather: true
|
||||
footstepSounds:
|
||||
collection: FootstepPlating
|
||||
friction: 1.5
|
||||
isSpace: true
|
||||
itemDrop: PartRodMetal1
|
||||
heatCapacity: 10000
|
||||
mass: 200
|
||||
|
|
|
|||
Loading…
Reference in New Issue