402 lines
17 KiB
C#
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;
|
|
}
|