コード例 #1
0
        public async Task<bool> MoveAsync(Point sourcePosition, Point targetPosition)
        {
            var source = await Map.GetAsync(sourcePosition);
            var target = await Map.GetAsync(targetPosition);
            var initiator = new MoveInitiator(source.Tile.Entity, sourcePosition);
            var transaction = new TransactionWithMoveSupport(initiator);
            var result = await Map.MoveAsync(sourcePosition, targetPosition, transaction);

            var request = new RemoteMoveRequest(
                new VersionedPoint(sourcePosition, source.Version),
                new VersionedPoint(targetPosition, target.Version),
                result.Transaction.Changes.Select(c => new VersionedPoint(c.Key, c.Value.Version)));

            var remoteMoveResult = await _serverStub.MoveAsync(request, PlayerInfo.PlayerId);

            if (remoteMoveResult.IsSuccessful)
            {
                // Commit transaction
                await transaction.CommitAsync(Map, remoteMoveResult.NewVersion);
                AnalyzeEvents(transaction.Events);
                return true;
            }
            else
            {
                // Our map was not up to date => apply updated chunks
                foreach (var kvp in remoteMoveResult.ChunkUpdates)
                    await ApplyChunkAsync(kvp.Value, kvp.Key);
            }

            return false;
        }
コード例 #2
0
        /// <inheritdoc/>
        public async Task<JoinResult> JoinAsync(GameClientInfo clientInfo, IGameClientStub clientStub)
        {
            using (await _clientsLock.LockAsync())
            {
                if (_clients.Values.Any(conn => conn.ClientInfo.PlayerId == clientInfo.PlayerId.ToString()))
                    return new JoinResult(false, Point.Zero, "Another client is already connected using the same player ID");

                var connection = new ClientConnection(clientInfo, clientStub);
                var playerEntity = new PlayerEntity(clientInfo.PlayerId, Point.North);

                // Check if the player is playing this map for the first time
                if (Map.Metadata.Players.IsKnown(clientInfo.PlayerId))
                {
                    // Try to spawn player at its last known position.
                    var playerInfo = Map.Metadata.Players[clientInfo.PlayerId];
                    var spawnTransaction = new TransactionWithMoveSupport(MoveInitiator.Empty);
                    var spawnResult = await Map.SpawnAsync(playerEntity, playerInfo.Position, spawnTransaction);

                    if (spawnResult.IsSuccessful)
                    {
                        // TODO: What if a player spawns within a level some others are currently trying to solve?
                        await CommitAndBroadcastAsync(spawnResult.Transaction, spawnResult.FollowUpEvents, connection);
                        _clients.Add(clientInfo.PlayerId, connection);
                        Map.Emit(new PlayerJoinEvent(clientInfo));
                        return new JoinResult(true, playerInfo.Position);
                    }
                    else
                    {
                        // If that fails,
                        // option 1: spawn at default spawn area
                        // option 2: return false
                        throw new NotImplementedException();
                    }
                }
                else
                {
                    // Unknown player: Spawn player somewhere in default spawn area, add to list of known players
                    var spawnPositions = await Map.GetCoherentPositionsAsync(Map.Metadata.Regions.DefaultRegion.SpawnPosition);
                    var spawnTransaction = new TransactionWithMoveSupport(MoveInitiator.Empty);
                    var spawnResult = await Map.SpawnAsync(playerEntity, spawnPositions, spawnTransaction);

                    if (spawnResult.IsSuccessful)
                    {
                        await CommitAndBroadcastAsync(spawnResult.Transaction, spawnResult.FollowUpEvents, connection);
                        var playerInfo = new PlayerInfo(clientInfo.PlayerId, clientInfo.PlayerDisplayName, spawnResult.SpawnPosition);
                        Map.Metadata.Players.Add(playerInfo);
                        _clients.Add(clientInfo.PlayerId, connection);
                        Map.Emit(new PlayerJoinEvent(clientInfo));
                        return new JoinResult(true, spawnResult.SpawnPosition);
                    }
                    else
                    {
                        // TODO: What now? We could not spawn the player!
                        throw new NotImplementedException();
                    }
                }
            }
        }
コード例 #3
0
        private async void OnChunkLoaded(KeyValuePair<Point, IChunk> kvp)
        {
            // TODO: Decide to what extent chunk loading should be
            //       handled in Map and in GameServer.

            // Properly initialize the chunk.
            // Example scenarios: On chunk loading...
            // - a button should be activated immediately if there's an entity pressing it
            // - a piston should extend immediately if its trigger is on
            // - a balloon should be popped immediately if it is on a pin
            var chunkPoints = new Rectangle(kvp.Key.X * Chunk.Size, kvp.Key.Y * Chunk.Size, Chunk.Size - 1, Chunk.Size - 1);
            var transaction = new TransactionWithMoveSupport(MoveInitiator.Empty);
            var followUpEvents = await ((Map)Map).UpdateTilesAsync(chunkPoints, transaction);

            using (await _clientsLock.LockAsync())
                await CommitAndBroadcastAsync(transaction, followUpEvents);
        }
コード例 #4
0
ファイル: Map.cs プロジェクト: SvenEV/OrangeBugReloaded
        private async void OnChunkLoaded(KeyValuePair<Point, IChunk> kvp)
        {
            for (var y = 0; y < Chunk.Size; y++)
            {
                for (var x = 0; x < Chunk.Size; x++)
                {
                    var localPosition = new Point(x, y);
                    var globalPosition = kvp.Key * Chunk.Size + localPosition;
                    var tileInfo = kvp.Value[localPosition];
                    Dependencies.AddDependenciesOf(tileInfo.Tile, globalPosition);

                    // For entities emit a spawn event
                    if (tileInfo.Tile.Entity != Entity.None)
                    {
                        var spawnEvent = new EntitySpawnEvent(globalPosition, tileInfo.Tile.Entity);
                        _eventSource.OnNext(spawnEvent);
                    }
                }
            }

            // Properly initialize the chunk.
            // Example scenarios: On chunk loading...
            // - a button should be activated immediately if there's an entity pressing it
            // - a balloon should be popped immediately if it is on a pin
            var chunkPoints = new Rectangle(kvp.Key.X * Chunk.Size, kvp.Key.Y * Chunk.Size, Chunk.Size - 1, Chunk.Size - 1);
            var transaction = new TransactionWithMoveSupport(MoveInitiator.Empty);
            await UpdateTilesAsync(chunkPoints, transaction);
            _eventSource.OnNext(new ChunkAddedEvent(kvp.Value));

            // TODO: This is problematic! A commit is done directly on the map
            // bypassing the GameServer. We might have to forward the new chunk
            // to some of the players.
            // On client side we can't just invent some new version numbers.
            await transaction.CommitAsync(this, Metadata?.NextTileVersion() ?? 0);
        }
コード例 #5
0
        private async Task RunAsync()
        {
            // 4 updates per second
            var targetElapsedTime = TimeSpan.FromSeconds(.25);

            while (true)
            {
                var startTime = DateTimeOffset.Now;

                // Run all scheduled moves that should have run until now
                while (_scheduledFollowUpEvents.Any() && _scheduledFollowUpEvents.First().ExecutionTime <= startTime)
                {
                    // Get the "oldest" follow-up event
                    var followUpEvent = _scheduledFollowUpEvents.OrderBy(e => e.ExecutionTime).First();
                    _scheduledFollowUpEvents.Remove(followUpEvent);

                    var tileInfo = await Map.GetAsync(followUpEvent.Position);

                    // Trigger follow-up transactions
                    // e.g. a teleporter might use this to teleport an entity
                    // (which can result in further follow-up events).
                    var transaction = new TransactionWithMoveSupport(followUpEvent.Initiator);
                    var args = new GameplayArgs(transaction, Map) as IFollowUpArgs;
                    await tileInfo.Tile.OnFollowUpTransactionAsync(args, followUpEvent.Position);

                    using (await _clientsLock.LockAsync())
                        await CommitAndBroadcastAsync(transaction, args.FollowUpEvents);
                }

                var elapsed = DateTimeOffset.Now - startTime;
                var remaining = targetElapsedTime - elapsed;

                if (remaining > TimeSpan.Zero)
                    await Task.Delay(remaining);
            }
        }
コード例 #6
0
        /// <inheritdoc/>
        public async Task<bool> ResetRegionAsync(string playerId)
        {
            using (await _clientsLock.LockAsync())
            {
                var player = Map.Metadata.Players[playerId];
                var resetTransaction = new TransactionWithMoveSupport(MoveInitiator.Empty);
                var resetResult = await Map.ResetRegionAsync(player.Position, resetTransaction);

                if (resetResult.IsSuccessful)
                {
                    await CommitAndBroadcastAsync(resetResult.Transaction, resetResult.FollowUpEvents);
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
コード例 #7
0
        /// <inheritdoc/>
        public async Task<RemoteMoveResult> MoveAsync(RemoteMoveRequest move, string playerId)
        {
            using (await _clientsLock.LockAsync())
            {
                var connection = GetClient(playerId);
                await connection.MoveSemaphore.WaitAsync();

                try
                {
                    var source = await Map.GetAsync(move.SourcePosition);
                    var target = await Map.GetAsync(move.TargetPosition);
                    var initiator = new MoveInitiator(source.Tile.Entity, move.SourcePosition);
                    var transaction = new TransactionWithMoveSupport(initiator);
                    var moveResult = await Map.MoveAsync(move.SourcePosition, move.TargetPosition, transaction);

                    // Compare affected tiles of the client and those of the server
                    var clientAffectedTiles = move.AffectedPositions.Union(new[] { move.SourcePosition, move.TargetPosition });
                    var serverAffectedTiles = moveResult.Transaction.Changes
                        .Select(c => new VersionedPoint(c.Key, c.Value.Version))
                        .Union(new[] { new VersionedPoint(move.SourcePosition, source.Version), new VersionedPoint(move.TargetPosition, target.Version) })
                        .ToList();

                    var versions = serverAffectedTiles.FullOuterJoin(clientAffectedTiles,
                        vp => vp.Position,
                        vp => vp.Position,
                        (serverVP, clientVP, p) => new { Position = p, ServerVersion = serverVP.Version, ClientVersion = clientVP.Version },
                        VersionedPoint.Empty,
                        VersionedPoint.Empty);

                    if (versions.Any(v => v.ClientVersion != -1 && v.ServerVersion != -1 && v.ClientVersion > v.ServerVersion))
                        throw new NotImplementedException("This should not happen. Clients must not have a newer version than the server");

                    var conflictingVersions = versions.Where(v =>
                        (v.ClientVersion == -1 && v.ServerVersion != -1) ||
                        (v.ClientVersion != -1 && v.ServerVersion == -1) ||
                        (v.ClientVersion != -1 && v.ServerVersion != -1 && v.ClientVersion < v.ServerVersion));

                    if (conflictingVersions.Any())
                    {
                        // Client not up to date => Cancel move request and send up-to-date chunks
                        var chunks = await Task.WhenAll(conflictingVersions
                            .Select(v => v.Position / Chunk.Size).Distinct()
                            .Select(async index => new KeyValuePair<Point, IChunk>(index, await Map.ChunkLoader.GetAsync(index))));

                        return RemoteMoveResult.CreateFaulted(chunks);
                    }
                    else
                    {
                        // Client and server are on the same version (regarding affected tiles)
                        // => Commit and schedule follow-up transactions
                        // => Notify other clients about the changes
                        var newVersion = await CommitAndBroadcastAsync(moveResult.Transaction, moveResult.FollowUpEvents, connection);
                        return RemoteMoveResult.CreateSuccessful(newVersion);
                    }
                }
                finally
                {
                    connection.MoveSemaphore.Release();
                }
            }
        }
コード例 #8
0
        /// <inheritdoc/>
        public async Task LeaveAsync(string playerId)
        {
            using (await _clientsLock.LockAsync())
            {
                var connection = GetClient(playerId);
                var playerInfo = Map.Metadata.Players[playerId];

                // Remove PlayerEntity from Map (despawn)
                var despawnTransaction = new TransactionWithMoveSupport(MoveInitiator.Empty);
                var despawnResult = await Map.DespawnAsync(playerInfo.Position, despawnTransaction);

                if (despawnResult.IsSuccessful)
                {
                    await CommitAndBroadcastAsync(despawnResult.Transaction, despawnResult.FollowUpEvents, connection);

                    // Unload all the chunks currently loaded by the client
                    while (connection.LoadedChunks.Any())
                        UnloadChunkCore(connection.LoadedChunks.First(), connection);

                    _clients.Remove(playerId);
                    Map.Emit(new PlayerLeaveEvent(connection.ClientInfo));
                }
                else
                {
                    // TODO: What now? We could not despawn the player!
                    throw new NotImplementedException();
                }
            }
        }