/// <summary>
        /// Cancel an active coin flip
        /// </summary>
        /// <param name="userID">User ID of the coin flip that is to be canceled</param>
        private void cancelCoinFlip(string userID)
        {
            // Argument validation
            if (String.IsNullOrEmpty(userID))
            {
                throw new ArgumentNullException("userID");
            }

            // Check if player has an active coin flip
            if (!this.coinFlips.ContainsKey(userID))
            {
                return;
            }

            // Get the coin flip parameters
            CoinFlipParams coinFlipParams = this.coinFlips[userID];

            // Destroy the timer that cancels the coin flip after the timeout
            coinFlipParams.Timer.Destroy();

            // Remove the coin flip from the active coin flip list
            this.coinFlips.Remove(userID);

            // Refund the balance to the player
            this.Economics.Call("Deposit", userID, (double)coinFlipParams.Amount);

            // Get the player if he is still online
            IPlayer player = this.covalence.Players.FindPlayerById(userID);

            if (player == null || !player.IsConnected)
            {
                return;
            }

            // Inform the player about the cancellation
            if (coinFlipParams.Target == null)
            {
                this.sendLocalisedMessage(player, Messages.COINFLIP_CANCELED, coinFlipParams.Amount);
            }
            else
            {
                this.sendLocalisedMessage(player, Messages.PRIVATE_COINFLIP_CANCELED, coinFlipParams.Amount, coinFlipParams.Target.Name);
            }
        }
        /// <summary>
        /// Flips a coin
        /// </summary>
        /// <param name="initiator">Player who initiated this coin flip</param>
        /// <param name="player">Player who has joined this coin flip</param>
        /// <param name="coinFlipParams">Parameters of this coin flip</param>
        private void flip(IPlayer initiator, IPlayer player, CoinFlipParams coinFlipParams)
        {
            // Argument validation
            if (initiator == null)
            {
                throw new ArgumentNullException("initiator");
            }

            if (player == null)
            {
                throw new ArgumentNullException("player");
            }

            // Check if both opponents are still connected
            if (!initiator.IsConnected || !player.IsConnected)
            {
                // Send abort message
                if (initiator.IsConnected)
                {
                    this.sendLocalisedMessage(initiator, Messages.LEFT, coinFlipParams.Amount);
                }
                if (player.IsConnected)
                {
                    this.sendLocalisedMessage(player, Messages.LEFT, coinFlipParams.Amount);
                }

                // Refund balance
                this.Economics.Call("Deposit", initiator.Id, (double)coinFlipParams.Amount);
                this.Economics.Call("Deposit", player.Id, (double)coinFlipParams.Amount);

                return;
            }

            // Pick a winner and loser by throwing a coin
            IPlayer winner, loser = null;

            if (CoinFlip.RNG.Next(0, 2) == 1)
            {
                winner = initiator;
                loser  = player;
            }
            else
            {
                winner = player;
                loser  = initiator;
            }

            // Inform both opponents about the result
            this.sendLocalisedMessage(initiator, Messages.COIN_LAND, winner.Name);
            this.sendLocalisedMessage(player, Messages.COIN_LAND, winner.Name);

            // Congratulate the winner, condole the loser
            this.sendLocalisedMessage(winner, Messages.WIN, coinFlipParams.Amount * 2);
            this.sendLocalisedMessage(loser, Messages.LOSS, coinFlipParams.Amount);

            // Deposit the pot to the winner
            this.Economics.Call("Deposit", winner.Id, (double)coinFlipParams.Amount * 2.0);

            // Announce result to everybody excluding the two opponents
            if (this.AnnounceEverybody)
            {
                this.covalence.Players.Connected.ToArray().Where(p => p.Id != player.Id && p.Id != initiator.Id).ToList().ForEach(p => this.sendLocalisedMessage(p, Messages.WIN_ALL, winner.Name, loser.Name, coinFlipParams.Amount * 2));
            }
        }
        private void joinFlipChatCommand(IPlayer player, string command, string[] args)
        {
            // Validate caller is a player that can interact with Economy
            if (player.IsServer)
            {
                player.Reply("This command can not be used from RCON.");
                return;
            }

            // Validate argument count
            if (this.sendConditionalLocalisedMessage(player, args.Length == 0, Messages.JOIN_USAGE))
            {
                return;
            }

            // Try and find the target player
            IPlayer targetPlayer = this.findPlayer(player, String.Join(" ", args));

            if (targetPlayer == null)
            {
                return;
            }

            // Prevent joining your own coin flip
            if (this.sendConditionalLocalisedMessage(player, player.Id == targetPlayer.Id, Messages.YOURSELF))
            {
                return;
            }

            // Check if target player has an active coin flip
            if (this.sendConditionalLocalisedMessage(player, !this.coinFlips.ContainsKey(targetPlayer.Id), Messages.NOT_FLIPPING, targetPlayer.Name))
            {
                return;
            }

            // Retrieve active coin flip of target player
            CoinFlipParams coinFlipParams = this.coinFlips[targetPlayer.Id];

            if (this.sendConditionalLocalisedMessage(player, coinFlipParams.Target != null && coinFlipParams.Target.Id != player.Id, Messages.PRIVATE))
            {
                return;
            }

            // Check and withdraw balance for coin flip
            double availableBalance = this.Economics.Call <double>("Balance", player.Id);

            if (this.sendConditionalLocalisedMessage(player, !this.Economics.Call <bool>("Withdraw", player.Id, (double)coinFlipParams.Amount), Messages.BALANCE_TOO_LOW, availableBalance, coinFlipParams.Amount))
            {
                return;
            }

            // Remove coin flip from active coin flips to prevent any other players from joining
            this.coinFlips.Remove(targetPlayer.Id);
            coinFlipParams.Timer.Destroy();

            // Inform both players about the commencing coin flip
            this.sendLocalisedMessage(targetPlayer, Messages.FLIP_JOINED, player.Name);
            this.sendLocalisedMessage(player, Messages.FLIP_JOIN, targetPlayer.Name, coinFlipParams.Amount);

            // Announce the coin flip to everybody else
            if (this.AnnounceEverybody)
            {
                this.covalence.Players.Connected.ToArray().Where(p => p.Id != player.Id && p.Id != targetPlayer.Id).ToList().ForEach(p => this.sendLocalisedMessage(p, Messages.JOIN_ALL, targetPlayer.Name, player.Name, coinFlipParams.Amount));
            }

            // Start 3 second countdown (it's actually 5 seconds, but sssssh)
            timer.Once(1, () =>
            {
                this.sendLocalisedMessage(player, Messages.COUNT_THREE);
                this.sendLocalisedMessage(targetPlayer, Messages.COUNT_THREE);
                timer.Once(1, () =>
                {
                    this.sendLocalisedMessage(player, Messages.COUNT_TWO);
                    this.sendLocalisedMessage(targetPlayer, Messages.COUNT_TWO);

                    timer.Once(1, () =>
                    {
                        this.sendLocalisedMessage(player, Messages.COUNT_ONE);
                        this.sendLocalisedMessage(targetPlayer, Messages.COUNT_ONE);

                        timer.Once(1, () =>
                        {
                            this.sendLocalisedMessage(player, Messages.FLIP);
                            this.sendLocalisedMessage(targetPlayer, Messages.FLIP);

                            // Start actually flipping
                            timer.Once(1, () => this.flip(targetPlayer, player, coinFlipParams));
                        });
                    });
                });
            });
        }