225 lines
8.7 KiB
C#
225 lines
8.7 KiB
C#
using System.Linq;
|
|
using Content.Server._DV.CosmicCult.Components;
|
|
using Content.Shared._DV.CosmicCult.Components;
|
|
using Content.Shared.Maps;
|
|
using Robust.Server.GameObjects;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Map.Components;
|
|
using Robust.Shared.Random;
|
|
using Robust.Shared.Timing;
|
|
|
|
namespace Content.Server._DV.CosmicCult.EntitySystems;
|
|
|
|
public sealed class CosmicCorruptingSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly MapSystem _map = default!;
|
|
|
|
private readonly HashSet<Vector2i> _neighbourPositions =
|
|
[
|
|
new(-1, 1),
|
|
new(0, 1),
|
|
new(1, 1),
|
|
new(-1, 0),
|
|
new(0, 0),
|
|
new(1, 0),
|
|
new(-1, -1),
|
|
new(0, -1),
|
|
new(1, -1),
|
|
];
|
|
|
|
[Dependency] private readonly IRobustRandom _rand = default!;
|
|
[Dependency] private readonly TileSystem _tile = default!;
|
|
[Dependency] private readonly ITileDefinitionManager _tileDefinition = default!;
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
[Dependency] private readonly TurfSystem _turfs = default!;
|
|
|
|
/// <remarks>
|
|
/// this system is a mostly generic way of replacing tiles around an entity. the only hardcoded behaviour is secret
|
|
/// walls -> malign doors, but that shouldn't be too hard to fix if this is needed for smth else later.
|
|
/// </remarks>
|
|
public override void Initialize()
|
|
{
|
|
SubscribeLocalEvent<CosmicCorruptingComponent, MapInitEvent>(OnMapInit);
|
|
}
|
|
|
|
//when the entity spawns, add all neighbouring tiles to the corruptable list
|
|
private void OnMapInit(Entity<CosmicCorruptingComponent> ent, ref MapInitEvent args)
|
|
{
|
|
RecalculateStartingTiles(ent);
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
var blanktimer = EntityQueryEnumerator<CosmicCorruptingComponent>();
|
|
while (blanktimer.MoveNext(out var uid, out var comp))
|
|
{
|
|
if (comp.Enabled && _timing.CurTime >= comp.CorruptionTimer)
|
|
{
|
|
comp.CorruptionTimer = _timing.CurTime + comp.CorruptionSpeed;
|
|
ConvertTiles((uid, comp));
|
|
if (comp.CorruptionTicks <= comp.CorruptionMaxTicks)
|
|
{
|
|
comp.CorruptionTicks++;
|
|
comp.CorruptionChance -= comp.CorruptionReduction;
|
|
}
|
|
|
|
if (comp.CorruptionTicks >= comp.CorruptionMaxTicks && comp.AutoDisable)
|
|
comp.Enabled =
|
|
false; //maybe just remComp this? atm nothing re-enables a corruptor so that should be safe to do?
|
|
}
|
|
}
|
|
}
|
|
|
|
private void ConvertTiles(Entity<CosmicCorruptingComponent> ent)
|
|
{
|
|
var xform = Transform(ent);
|
|
if (xform.GridUid is not { } gridUid || !TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
|
return;
|
|
|
|
var convertTile = (ContentTileDefinition)_tileDefinition[ent.Comp.ConversionTile];
|
|
|
|
//if this is a mobile corruptor, reset the list of corruptable tiles every attempt.
|
|
//not a super clean solution because I didn't account for the astral nova in the first rewrite but it works well enough for our purposes.
|
|
if (ent.Comp.Mobile)
|
|
RecalculateStartingTiles(ent);
|
|
|
|
//go over every corruptible tile
|
|
foreach (var pos in
|
|
new HashSet<Vector2i>(ent.Comp.CorruptableTiles)) //we love avoiding ConcurrentModificationExceptions
|
|
{
|
|
var tileRef = _map.GetTileRef((gridUid, mapGrid), pos);
|
|
if (tileRef.Tile.TypeId == convertTile.TileId ||
|
|
tileRef.Tile.IsEmpty) //if it's already corrupted (or space), remove it from the list and continue
|
|
{
|
|
ent.Comp.CorruptableTiles.Remove(pos);
|
|
continue;
|
|
}
|
|
|
|
if (_rand.Prob(ent.Comp.CorruptionChance)) //if it rolls good
|
|
{
|
|
//replace & variantise the tile
|
|
_tile.ReplaceTile(tileRef, convertTile);
|
|
_tile.PickVariant(convertTile);
|
|
|
|
//then add the new neighbours as targets as long as they're not already corrupted
|
|
foreach (var neighbourPos in _neighbourPositions)
|
|
{
|
|
var neighbourRef = _map.GetTileRef((gridUid, mapGrid), tileRef.GridIndices + neighbourPos);
|
|
if (neighbourRef.Tile.TypeId == convertTile.TileId ||
|
|
tileRef.Tile.IsEmpty) //ignore already corrupted (or space) tiles
|
|
continue;
|
|
|
|
ent.Comp.CorruptableTiles.Add(neighbourRef.GridIndices);
|
|
}
|
|
|
|
//corrupt anything that can be corrupted
|
|
foreach (var convertedEnt in _map.GetAnchoredEntities((gridUid, mapGrid), pos).ToList())
|
|
{
|
|
var proto = Prototype(convertedEnt);
|
|
if (ent.Comp.EntityConversionDict.TryGetValue(proto?.ID!, out var conversion))
|
|
{
|
|
Spawn(conversion, Transform(convertedEnt).Coordinates);
|
|
QueueDel(convertedEnt);
|
|
}
|
|
else if (TryComp<CosmicCorruptibleComponent>(convertedEnt, out var corruptible))
|
|
{
|
|
Spawn(corruptible.ConvertTo, Transform(convertedEnt).Coordinates);
|
|
QueueDel(convertedEnt);
|
|
}
|
|
}
|
|
|
|
//spawn the vfx if we should
|
|
if (ent.Comp.UseVFX)
|
|
Spawn(ent.Comp.TileConvertVFX, _turfs.GetTileCenter(tileRef));
|
|
|
|
ent.Comp.CorruptableTiles.Remove(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
#region API
|
|
|
|
public void SetCorruptionTime(Entity<CosmicCorruptingComponent> ent, TimeSpan time)
|
|
{
|
|
ent.Comp.CorruptionSpeed = time;
|
|
}
|
|
|
|
public void Enable(Entity<CosmicCorruptingComponent> ent, bool recalculate = true)
|
|
{
|
|
ent.Comp.Enabled = true;
|
|
|
|
if (recalculate)
|
|
RecalculateStartingTiles(ent);
|
|
}
|
|
|
|
public void RecalculateStartingTiles(Entity<CosmicCorruptingComponent> ent)
|
|
{
|
|
ent.Comp.CorruptableTiles.Clear();
|
|
|
|
var xform = Transform(ent);
|
|
|
|
if (xform.GridUid is not { } gridUid || !TryComp<MapGridComponent>(gridUid, out var mapGrid))
|
|
return;
|
|
|
|
var grid = (gridUid, mapGrid);
|
|
var tile = _map.GetTileRef(grid, xform.Coordinates);
|
|
|
|
if (ent.Comp.FloodFillStarting) //todo make this async? it doesn't actually run that much though
|
|
{
|
|
var convertTile = (ContentTileDefinition)_tileDefinition[ent.Comp.ConversionTile];
|
|
var visitedTiles = new HashSet<Vector2i>();
|
|
var tilesToVisit = new HashSet<Vector2i> { tile.GridIndices };
|
|
|
|
var count = 0;
|
|
|
|
while (tilesToVisit.Count > 0)
|
|
{
|
|
//get the first tile in the list
|
|
var currtile = tilesToVisit.First();
|
|
count++;
|
|
|
|
//check every neighbouring tile
|
|
foreach (var neighbourPos in _neighbourPositions)
|
|
{
|
|
var neighbourRef = _map.GetTileRef((gridUid, mapGrid), currtile + neighbourPos);
|
|
|
|
//if it's already been converted
|
|
if (neighbourRef.Tile.TypeId == convertTile.TileId)
|
|
{
|
|
//and not already visited
|
|
if (!visitedTiles.Contains(neighbourRef.GridIndices))
|
|
tilesToVisit.Add(neighbourRef.GridIndices); //add it to the to visit list
|
|
}
|
|
else
|
|
{
|
|
//else, it's not been converted, so mark it as visited and add it to the corruptible tiles list
|
|
//we don't care if the tile is empty, that'll get checked later
|
|
visitedTiles.Add(neighbourRef.GridIndices);
|
|
ent.Comp.CorruptableTiles.Add(neighbourRef.GridIndices);
|
|
}
|
|
}
|
|
|
|
//finally, mark the tile as visited and remove it from the toVisit list
|
|
visitedTiles.Add(currtile);
|
|
tilesToVisit.Remove(currtile);
|
|
}
|
|
|
|
Log.Info($"floodfill tile recaulculation ran {count} times");
|
|
}
|
|
else
|
|
{
|
|
//add every neighbouring tile to the corruptable list
|
|
//don't bother checking eligibility at this point because it'll get done later anyway
|
|
foreach (var neighbourPos in _neighbourPositions)
|
|
{
|
|
var neighbourRef = _map.GetTileRef((gridUid, mapGrid), tile.GridIndices + neighbourPos);
|
|
|
|
ent.Comp.CorruptableTiles.Add(neighbourRef.GridIndices);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|