/// <inheritdoc/> public async Task<bool> SetAsync(Point position, TileInfo tileInfo) { // TODO: Handle concurrency var chunk = await ChunkLoader.GetAsync(position / Chunk.Size); var oldTileInfo = chunk[position % Chunk.Size]; if (!Equals(oldTileInfo, tileInfo)) { // Update chunk chunk[position % Chunk.Size] = tileInfo; // Update dependencies Dependencies.RemoveDependenciesOf(oldTileInfo.Tile, position); Dependencies.AddDependenciesOf(tileInfo.Tile, position); return true; } return false; }
/// <inheritdoc/> public async Task<ResetResult> ResetRegionAsync(Point position, ITransactionWithMoveSupport transaction) { // TOOD: Do not modify map directly, collect changes in a transaction // instead of 'bool' return 'ResetResult' containing the transaction var tileMeta = await GetMetadataAsync(position); var region = Metadata.Regions[tileMeta.RegionId]; var regionPoints = await this.GetCoherentPositionsAsync(position); var playersInRegion = new List<PlayerEntity>(); // Despawn all players within region foreach (var p in regionPoints) { var tileInfo = await GetAsync(p); var player = tileInfo.Tile.Entity as PlayerEntity; if (player != null) { playersInRegion.Add(player); var despawnResult = await DespawnAsync(p, transaction); if (!despawnResult.IsSuccessful) return new ResetResult(transaction, false, Enumerable.Empty<FollowUpEvent>()); // If we can't despawn a player in the region, we can't reset } } // Replace tiles with their templates foreach (var p in regionPoints) { var meta = await GetMetadataAsync(p); var oldTileInfo = await GetAsync(p); var newTileInfo = new TileInfo(meta.TileTemplate, oldTileInfo.Version); // Version will be set during commit var hasChanged = transaction.Set(p, oldTileInfo, newTileInfo); if (hasChanged && oldTileInfo.Tile.Entity != Entity.None) transaction.Emit(new EntityDespawnEvent(p, oldTileInfo.Tile.Entity)); if (hasChanged && newTileInfo.Tile.Entity != Entity.None) transaction.Emit(new EntitySpawnEvent(p, newTileInfo.Tile.Entity)); } // Respawn players at spawn position var spawnPoints = await this.GetCoherentPositionsAsync(region.SpawnPosition); var availableSpawnPoints = spawnPoints.Shuffle().ToQueue(); if (availableSpawnPoints.Count < playersInRegion.Count) return new ResetResult(transaction, false, Enumerable.Empty<FollowUpEvent>()); // Spawn region is too small to respawn all players foreach (var player in playersInRegion) { var success = false; var spawnPosition = Point.Zero; while (!success && availableSpawnPoints.Any()) { transaction.IsSealed = false; // If the last spawn failed the transaction is sealed which would prevent any other spawns spawnPosition = availableSpawnPoints.Dequeue(); var spawnResult = await SpawnAsync(player, spawnPosition, transaction); success = spawnResult.IsSuccessful; } if (success) { // Update player position in map metadata Metadata.Players[player.PlayerId] = Metadata.Players[player.PlayerId].WithPosition(spawnPosition); } else { // We ran out of available spawn positions and could not spawn the player // => we can't fully reset the region return new ResetResult(transaction, false, Enumerable.Empty<FollowUpEvent>()); } } // Notify changed tiles and tiles that directly or indirectly depend on changed tiles var affectedTiles = transaction.Changes.Select(kvp => kvp.Key); var followUpEvents = await UpdateTilesAsync(affectedTiles, transaction); return new ResetResult(transaction, true, followUpEvents); }