sandpypi/Sand.ChunkPrototype/PrototypeSparseSandAdapter.Cells.cs

402 lines
17 KiB
C#

using Sand.Core;
namespace Sand.ChunkPrototype;
public sealed partial class PrototypeSparseSandAdapter
{
public bool AddParticle(int x, int y, PrototypeParticle particle)
{
if (!InBounds(x, y) || particle.IsEmpty)
{
return false;
}
RegisterParticleProfile(particle);
var coord = GetChunkCoord(x, y);
var (localX, localY) = GetLocalCoord(x, y);
var page = GetOrCreateCellPage(coord);
if (page.IsOccupied(localX, localY))
{
if (particle.MotionType == PrototypeParticleType.Wall)
{
page.SetCell(localX, localY, particle);
page.SetDriftState(localX, localY, 0);
InitializeRuntimeState(page, localX, localY, particle);
page.LastTouchedFrame = _stepCounter;
MarkChunkActive(coord, page);
World.SetOccupied(x, y, (byte)particle.MotionType);
return true;
}
return false;
}
page.SetCell(localX, localY, particle);
page.SetDriftState(localX, localY, GetDefaultDriftDirection(x, y));
InitializeRuntimeState(page, localX, localY, particle);
page.LastTouchedFrame = _stepCounter;
_particleCount++;
if (_fieldPages.TryGetValue(coord, out var fieldPage))
{
fieldPage.SetCell(localX, localY, default);
page.HasFieldActivity = !fieldPage.IsEmpty();
}
World.SetOccupied(x, y, (byte)particle.MotionType);
MarkChunkActive(coord, page);
WakeNeighborsForBorderTouch(coord, localX, localY);
return true;
}
public bool HasParticle(int x, int y) => TryGetParticle(x, y, out _);
public bool TryGetParticle(int x, int y, out PrototypeParticle particle)
{
particle = default;
if (!TryGetCellPage(x, y, out _, out var page, out var localX, out var localY))
{
return false;
}
particle = page[localX, localY];
return particle.TypeId != 0;
}
public PrototypeParticleType GetParticleTypeAt(int x, int y) => TryGetParticle(x, y, out var particle) ? particle.MotionType : PrototypeParticleType.Empty;
public ushort GetTypeIdAt(int x, int y) => TryGetParticle(x, y, out var particle) ? particle.TypeId : (ushort)0;
public float GetTemperatureAt(int x, int y)
{
if (!TryGetCellPage(x, y, out _, out var page, out var localX, out var localY))
{
return _ambientTemperature;
}
return page.GetTemperature(localX, localY);
}
public bool RemoveParticle(int x, int y)
{
if (!TryGetCellPage(x, y, out var coord, out var page, out var localX, out var localY))
{
return false;
}
var particle = page[localX, localY];
if (particle.TypeId == 0)
{
return false;
}
page.SetCell(localX, localY, default);
page.SetDriftState(localX, localY, 0);
page.LastTouchedFrame = _stepCounter;
_particleCount--;
World.ClearOccupied(x, y);
MarkChunkActive(coord, page);
WakeNeighborsForBorderTouch(coord, localX, localY);
RemoveEmptyCellPageIfUnused(coord, page);
return true;
}
private bool MoveParticle(int fromX, int fromY, int toX, int toY)
{
if (!TryGetCellPage(fromX, fromY, out var sourceCoord, out var sourcePage, out var sourceLocalX, out var sourceLocalY))
{
return false;
}
var particle = sourcePage[sourceLocalX, sourceLocalY];
if (particle.TypeId == 0)
{
return false;
}
var destinationCoord = GetChunkCoord(toX, toY);
var (destinationLocalX, destinationLocalY) = GetLocalCoord(toX, toY);
var destinationPage = GetOrCreateCellPage(destinationCoord);
if (destinationPage.IsOccupied(destinationLocalX, destinationLocalY))
{
return false;
}
var driftState = sourcePage.GetDriftState(sourceLocalX, sourceLocalY);
var pressureDuration = sourcePage.GetPressureDuration(sourceLocalX, sourceLocalY);
var temperature = sourcePage.GetTemperature(sourceLocalX, sourceLocalY);
var burnTime = sourcePage.GetBurnTime(sourceLocalX, sourceLocalY);
var burning = sourcePage.GetBurning(sourceLocalX, sourceLocalY);
var sparkTime = sourcePage.GetSparkTime(sourceLocalX, sourceLocalY);
var lifetime = sourcePage.GetLifetime(sourceLocalX, sourceLocalY);
var cellAge = sourcePage.GetCellAge(sourceLocalX, sourceLocalY);
var integrity = sourcePage.GetIntegrity(sourceLocalX, sourceLocalY);
sourcePage.SetCell(sourceLocalX, sourceLocalY, default);
sourcePage.SetDriftState(sourceLocalX, sourceLocalY, 0);
sourcePage.SetPressureDuration(sourceLocalX, sourceLocalY, 0f);
destinationPage.SetCell(destinationLocalX, destinationLocalY, particle);
destinationPage.SetDriftState(destinationLocalX, destinationLocalY, ResolveDriftAfterMove(fromX, toX, driftState));
destinationPage.SetPressureDuration(destinationLocalX, destinationLocalY, pressureDuration);
destinationPage.SetTemperature(destinationLocalX, destinationLocalY, temperature);
destinationPage.SetBurnTime(destinationLocalX, destinationLocalY, burnTime);
destinationPage.SetBurning(destinationLocalX, destinationLocalY, burning);
destinationPage.SetSparkTime(destinationLocalX, destinationLocalY, sparkTime);
destinationPage.SetLifetime(destinationLocalX, destinationLocalY, lifetime);
destinationPage.SetCellAge(destinationLocalX, destinationLocalY, cellAge);
destinationPage.SetIntegrity(destinationLocalX, destinationLocalY, integrity);
destinationPage.MarkProcessed(destinationLocalX, destinationLocalY, _stepCounter);
sourcePage.LastTouchedFrame = _stepCounter;
destinationPage.LastTouchedFrame = _stepCounter;
MarkChunkActive(sourceCoord, sourcePage);
MarkChunkActive(destinationCoord, destinationPage);
World.MoveOccupied(fromX, fromY, toX, toY, (byte)particle.MotionType);
WakeNeighborsForBorderTouch(sourceCoord, sourceLocalX, sourceLocalY);
WakeNeighborsForBorderTouch(destinationCoord, destinationLocalX, destinationLocalY);
RemoveEmptyCellPageIfUnused(sourceCoord, sourcePage);
_movedParticles++;
return true;
}
private bool MoveParticleWithinPage(ChunkCoord coord, ChunkCellPage page, int fromLocalX, int fromLocalY, int toLocalX, int toLocalY, int fromX, int fromY, int toX, int toY)
{
var particle = page[fromLocalX, fromLocalY];
if (particle.TypeId == 0 || page.IsOccupied(toLocalX, toLocalY))
{
return false;
}
var driftState = page.GetDriftState(fromLocalX, fromLocalY);
var pressureDuration = page.GetPressureDuration(fromLocalX, fromLocalY);
var temperature = page.GetTemperature(fromLocalX, fromLocalY);
var burnTime = page.GetBurnTime(fromLocalX, fromLocalY);
var burning = page.GetBurning(fromLocalX, fromLocalY);
var sparkTime = page.GetSparkTime(fromLocalX, fromLocalY);
var lifetime = page.GetLifetime(fromLocalX, fromLocalY);
var cellAge = page.GetCellAge(fromLocalX, fromLocalY);
var integrity = page.GetIntegrity(fromLocalX, fromLocalY);
page.SetCell(fromLocalX, fromLocalY, default);
page.SetDriftState(fromLocalX, fromLocalY, 0);
page.SetPressureDuration(fromLocalX, fromLocalY, 0f);
page.SetCell(toLocalX, toLocalY, particle);
page.SetDriftState(toLocalX, toLocalY, ResolveDriftAfterMove(fromX, toX, driftState));
page.SetPressureDuration(toLocalX, toLocalY, pressureDuration);
page.SetTemperature(toLocalX, toLocalY, temperature);
page.SetBurnTime(toLocalX, toLocalY, burnTime);
page.SetBurning(toLocalX, toLocalY, burning);
page.SetSparkTime(toLocalX, toLocalY, sparkTime);
page.SetLifetime(toLocalX, toLocalY, lifetime);
page.SetCellAge(toLocalX, toLocalY, cellAge);
page.SetIntegrity(toLocalX, toLocalY, integrity);
page.MarkProcessed(toLocalX, toLocalY, _stepCounter);
page.LastTouchedFrame = _stepCounter;
MarkChunkActive(coord, page);
World.MoveOccupiedWithinChunk(coord.X, coord.Y, fromLocalX, fromLocalY, toLocalX, toLocalY, (byte)particle.MotionType);
WakeNeighborsForBorderTouch(coord, fromLocalX, fromLocalY);
WakeNeighborsForBorderTouch(coord, toLocalX, toLocalY);
_movedParticles++;
return true;
}
private bool SwapParticles(int firstX, int firstY, int secondX, int secondY)
{
if (!TryGetCellPage(firstX, firstY, out var firstCoord, out var firstPage, out var firstLocalX, out var firstLocalY) ||
!TryGetCellPage(secondX, secondY, out var secondCoord, out var secondPage, out var secondLocalX, out var secondLocalY))
{
return false;
}
var firstParticle = firstPage[firstLocalX, firstLocalY];
var secondParticle = secondPage[secondLocalX, secondLocalY];
if (firstParticle.TypeId == 0 || secondParticle.TypeId == 0)
{
return false;
}
var firstDrift = firstPage.GetDriftState(firstLocalX, firstLocalY);
var secondDrift = secondPage.GetDriftState(secondLocalX, secondLocalY);
var firstPressureDuration = firstPage.GetPressureDuration(firstLocalX, firstLocalY);
var secondPressureDuration = secondPage.GetPressureDuration(secondLocalX, secondLocalY);
var firstTemperature = firstPage.GetTemperature(firstLocalX, firstLocalY);
var secondTemperature = secondPage.GetTemperature(secondLocalX, secondLocalY);
var firstBurnTime = firstPage.GetBurnTime(firstLocalX, firstLocalY);
var secondBurnTime = secondPage.GetBurnTime(secondLocalX, secondLocalY);
var firstBurning = firstPage.GetBurning(firstLocalX, firstLocalY);
var secondBurning = secondPage.GetBurning(secondLocalX, secondLocalY);
var firstSparkTime = firstPage.GetSparkTime(firstLocalX, firstLocalY);
var secondSparkTime = secondPage.GetSparkTime(secondLocalX, secondLocalY);
var firstLifetime = firstPage.GetLifetime(firstLocalX, firstLocalY);
var secondLifetime = secondPage.GetLifetime(secondLocalX, secondLocalY);
var firstAge = firstPage.GetCellAge(firstLocalX, firstLocalY);
var secondAge = secondPage.GetCellAge(secondLocalX, secondLocalY);
var firstIntegrity = firstPage.GetIntegrity(firstLocalX, firstLocalY);
var secondIntegrity = secondPage.GetIntegrity(secondLocalX, secondLocalY);
firstPage.SetCell(firstLocalX, firstLocalY, secondParticle);
firstPage.SetDriftState(firstLocalX, firstLocalY, ResolveDriftAfterMove(secondX, firstX, secondDrift));
firstPage.SetPressureDuration(firstLocalX, firstLocalY, secondPressureDuration);
firstPage.SetTemperature(firstLocalX, firstLocalY, secondTemperature);
firstPage.SetBurnTime(firstLocalX, firstLocalY, secondBurnTime);
firstPage.SetBurning(firstLocalX, firstLocalY, secondBurning);
firstPage.SetSparkTime(firstLocalX, firstLocalY, secondSparkTime);
firstPage.SetLifetime(firstLocalX, firstLocalY, secondLifetime);
firstPage.SetCellAge(firstLocalX, firstLocalY, secondAge);
firstPage.SetIntegrity(firstLocalX, firstLocalY, secondIntegrity);
secondPage.SetCell(secondLocalX, secondLocalY, firstParticle);
secondPage.SetDriftState(secondLocalX, secondLocalY, ResolveDriftAfterMove(firstX, secondX, firstDrift));
secondPage.SetPressureDuration(secondLocalX, secondLocalY, firstPressureDuration);
secondPage.SetTemperature(secondLocalX, secondLocalY, firstTemperature);
secondPage.SetBurnTime(secondLocalX, secondLocalY, firstBurnTime);
secondPage.SetBurning(secondLocalX, secondLocalY, firstBurning);
secondPage.SetSparkTime(secondLocalX, secondLocalY, firstSparkTime);
secondPage.SetLifetime(secondLocalX, secondLocalY, firstLifetime);
secondPage.SetCellAge(secondLocalX, secondLocalY, firstAge);
secondPage.SetIntegrity(secondLocalX, secondLocalY, firstIntegrity);
firstPage.MarkProcessed(firstLocalX, firstLocalY, _stepCounter);
secondPage.MarkProcessed(secondLocalX, secondLocalY, _stepCounter);
firstPage.LastTouchedFrame = _stepCounter;
secondPage.LastTouchedFrame = _stepCounter;
MarkChunkActive(firstCoord, firstPage);
MarkChunkActive(secondCoord, secondPage);
if (firstCoord == secondCoord)
{
World.SwapOccupiedWithinChunk(firstCoord.X, firstCoord.Y, firstLocalX, firstLocalY, secondLocalX, secondLocalY, (byte)firstParticle.MotionType, (byte)secondParticle.MotionType);
}
else
{
World.SwapOccupied(firstX, firstY, secondX, secondY, (byte)firstParticle.MotionType, (byte)secondParticle.MotionType);
}
WakeNeighborsForBorderTouch(firstCoord, firstLocalX, firstLocalY);
WakeNeighborsForBorderTouch(secondCoord, secondLocalX, secondLocalY);
_swappedParticles++;
return true;
}
private bool ReplaceParticle(int x, int y, PrototypeParticle particle)
{
if (!TryGetCellPage(x, y, out var coord, out var page, out var localX, out var localY))
{
return false;
}
if (particle.IsEmpty)
{
return RemoveParticle(x, y);
}
RegisterParticleProfile(particle);
page.SetCell(localX, localY, particle);
page.SetDriftState(localX, localY, GetDefaultDriftDirection(x, y));
InitializeRuntimeState(page, localX, localY, particle);
page.LastTouchedFrame = _stepCounter;
page.MarkProcessed(localX, localY, _stepCounter);
MarkChunkActive(coord, page);
World.SetOccupied(x, y, (byte)particle.MotionType);
WakeNeighborsForBorderTouch(coord, localX, localY);
return true;
}
private void InitializeRuntimeState(ChunkCellPage page, int localX, int localY, PrototypeParticle particle)
{
page.SetPressureDuration(localX, localY, 0f);
page.SetTemperature(localX, localY, particle.InitialTemperature);
page.SetBurnTime(localX, localY, particle.BurnDuration);
page.SetBurning(localX, localY, particle.BurningInit ? (byte)1 : (byte)0);
page.SetSparkTime(localX, localY, 0);
page.SetLifetime(localX, localY, particle.DefaultLifetime);
page.SetCellAge(localX, localY, 0f);
page.SetIntegrity(localX, localY, particle.Durability);
}
private bool TryResolveProfile(ushort typeId, out PrototypeParticle particle) => _particleProfiles.TryGetValue(typeId, out particle);
private bool TryFindProfileById(string particleId, out PrototypeParticle particle)
{
foreach (var profile in _particleProfiles.Values)
{
if (profile.TypeId != 0 && string.Equals(profile.Id, particleId, StringComparison.OrdinalIgnoreCase))
{
particle = profile;
return true;
}
}
particle = default;
return false;
}
private ChunkCoord GetChunkCoord(int x, int y) => new(x / _config.ChunkWidth, y / _config.ChunkHeight);
private (int LocalX, int LocalY) GetLocalCoord(int x, int y) => (x % _config.ChunkWidth, y % _config.ChunkHeight);
private bool TryGetCellPage(int x, int y, out ChunkCoord coord, out ChunkCellPage page, out int localX, out int localY)
{
coord = default;
page = null!;
localX = 0;
localY = 0;
if (!InBounds(x, y))
{
return false;
}
coord = GetChunkCoord(x, y);
if (!_cellPages.TryGetValue(coord, out page!))
{
return false;
}
(localX, localY) = GetLocalCoord(x, y);
return true;
}
private bool TryGetFieldPage(int x, int y, out ChunkCoord coord, out ChunkFieldPage page, out int localX, out int localY)
{
coord = default;
page = null!;
localX = 0;
localY = 0;
if (!InBounds(x, y))
{
return false;
}
coord = GetChunkCoord(x, y);
if (!_fieldPages.TryGetValue(coord, out page!))
{
return false;
}
(localX, localY) = GetLocalCoord(x, y);
return true;
}
private ChunkCellPage GetOrCreateCellPage(ChunkCoord coord)
{
if (_cellPages.TryGetValue(coord, out var page))
{
return page;
}
page = new ChunkCellPage(_config.ChunkWidth, _config.ChunkHeight);
_cellPages[coord] = page;
return page;
}
private ChunkFieldPage GetOrCreateFieldPage(ChunkCoord coord)
{
if (_fieldPages.TryGetValue(coord, out var page))
{
return page;
}
page = new ChunkFieldPage(_config.ChunkWidth, _config.ChunkHeight);
_fieldPages[coord] = page;
return page;
}
private int ToWorldX(ChunkCoord coord, int localX) => (coord.X * _config.ChunkWidth) + localX;
private int ToWorldY(ChunkCoord coord, int localY) => (coord.Y * _config.ChunkHeight) + localY;
private bool InBounds(int x, int y) => x >= 0 && x < Width && y >= 0 && y < Height;
}